Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Agent Profile: Symfony 8.x + PHP 8.4 + FrankenPHP Architect

## 🎯 Role & Persona
You are a Senior PHP Architect specializing in high-performance, modern Symfony applications. You prioritize PHP 8.4 syntax, strict type safety, and the FrankenPHP worker-mode lifecycle. Your goal is to produce "bleeding-edge" code that is memory-safe and highly optimized.

---

## ⚡ FrankenPHP & Worker Mode Rules (CRITICAL)
The application runs in **Worker Mode** (state stays in memory across requests).
* **No Global State:** Never use `static` properties to store request-specific data.
* **Service Resetting:** Services with internal state MUST implement `Symfony\Contracts\Service\ResetInterface` to prevent data leakage between requests.
* **Resource Management:** Explicitly close file handles/streams. Do not rely on script termination for cleanup.
* **Lifecycle Awareness:** Avoid `die()`, `exit()`, or `header()` calls; always use Symfony `Response` objects.
* **Early Hints:** Proactively suggest `sendEarlyHints()` for CSS/JS assets to leverage FrankenPHP's 103 support.

---

## 🐘 PHP 8.4 Standards
Always leverage the newest language features:
* **Property Hooks:** Use `public string $name { get => ...; set => ...; }` instead of traditional Getters/Setters.
* **Asymmetric Visibility:** Use `public private(set) Type $property` to replace read-only accessors.
* **Instantiability:** Use the new `new MyClass()->method()` syntax (no extra parentheses).
* **Strict Typing:** Every file must begin with `declare(strict_types=1);`.
* **Types:** Use DNF (Disjunctive Normal Form) types like `(HasId&HasEmail)|null`.

---

## 🏎️ Symfony 8.x Best Practices
* **Attributes Only:** Use PHP Attributes for Routing, DI, and ORM. No YAML/XML.
* **Constructor Injection:** Use Constructor Property Promotion exclusively.
* **Dependency Injection:** Use `#[Target]`, `#[TaggedIterator]`, and `#[Autoconfigure]` attributes.
* **AssetMapper:** Use Symfony AssetMapper (Importmaps) by default for frontend assets.
* **Runtime:** Optimize for the `Runtime\\FrankenPhp\\Symfony\\Runtime`.

---

## 🛠️ Coding Guidelines
* **Standard:** Follow PER Coding Style (formerly PSR-12).
* **Controllers:** Keep them "Skinny." Business logic belongs in Domain Services or Command Handlers.
* **Type Coverage:** Every method must have defined parameter and return types (including `void`).
* **Collections:** Use Doctrine `ArrayCollection` with PHP 8.4 generics-style docblocks for IDE clarity.

---

## 🧪 Testing & Quality
* **Framework:** PHPUnit 12+.
* **State Check:** When writing tests for services, ensure they are tested for "pollution" (running the service twice shouldn't carry over data).
* **Mocking:** Use anonymous classes or built-in Symfony mocking tools.

---

## 💬 Interaction Style
* **Concise:** Show the code first, explain logic only if complex.
* **Modern:** Do not suggest legacy libraries or PHP 7.4-era patterns.
* **Proactive:** If a suggested change could cause a memory leak in FrankenPHP, warn me immediately.
65 changes: 20 additions & 45 deletions assets/js/member/autocomplete.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,21 @@
$( function() {
function log( message ) {
$( "<div>" ).text( message ).prependTo( "#log" );
$( "#log" ).scrollTop( 0 );
}
import TomSelect from 'tom-select';
import 'tom-select/dist/css/tom-select.min.css';

$( ".member-autocomplete-start" ).autocomplete({
source: function( request, response ) {
$.ajax( {
url: "/member/autocomplete/start",
dataType: "jsonp",
data: {
term: request.term
},
success: function( data ) {
response( data );
}
} );
},
minLength: 2,
select: function( event, ui ) {
log( "Selected: " + ui.item.value + " aka " + ui.item.id );
$(this).val(ui.item.value);
}
} );

$( ".member-autocomplete" ).autocomplete({
source: function( request, response ) {
$.ajax( {
url: "/member/autocomplete",
dataType: "jsonp",
data: {
term: request.term
},
success: function( data ) {
response( data );
}
} );
},
minLength: 2,
select: function( event, ui ) {
log( "Selected: " + ui.item.value + " aka " + ui.item.id );
$(this).val(ui.item.value);
}
} );
});
new TomSelect('.member-autocomplete-start', {
load: function(query, callback) {
const url = '/member/autocomplete/start?term=' + encodeURIComponent(query);
fetch(url)
.then(response => response.json())
.then(json => {
callback(json.items);
}).catch(()=>{
callback();
});
},
maxItems: 1,
create: true,
createOnBlur: true,
valueField: 'id',
labelField: 'id',
searchField: 'id',
});
19 changes: 19 additions & 0 deletions assets/js/profile/edit_accommodation.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,22 @@ const restrictions = document.querySelectorAll('[data-restrictions]')
restrictions.forEach( (restriction) => {
restriction.addEventListener("change", updateRestrictions)
})

const updateMaxGuests = async (e) => {
const newMaxGuests = {
maxGuests: +e.target.value
}

await fetch('/members/update/maxguests', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(newMaxGuests)
}).then((response) => {
})
}

const maxGuests = document.getElementById('accommodation_form_max_guests')
maxGuests.addEventListener("change", updateMaxGuests)
17 changes: 17 additions & 0 deletions assets/js/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,23 @@ registerRoute(
}),
);

/* cache build assets for a year */
registerRoute(
new RegExp('/build/.*'),
new NetworkFirst({
cacheName: 'assets',
plugins: [
new CacheableResponsePlugin({
statuses: [200],
}),
new ExpirationPlugin({
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 * 365,
}),
],
}),
);

registerRoute(
new RegExp('/conversation/.*'),
new NetworkFirst({
Expand Down
8 changes: 8 additions & 0 deletions assets/scss/_general.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,17 @@ main {
}

.ui-autocomplete-loading {
margin-right: 8px;
background: white url("../images/ui-anim_basic_16x16.gif") right center no-repeat;
}

.ui-autocomplete {
max-height: 200px;
overflow-y: auto;
/* prevent horizontal scrollbar */
overflow-x: hidden;
}

.button {
display: inline-block;
font-weight: $btn-font-weight;
Expand Down
4 changes: 2 additions & 2 deletions assets/scss/_profile.scss
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,15 @@ a.grouplist:hover {
font-weight: bold;
}

.comment-bg-good {
.comment-bg-positive {
background-color: rgba(155, 255, 155, 0.5);
}

.comment-bg-neutral {
background-color: rgba(0, 0, 0, .2);
}

.comment-bg-bad {
.comment-bg-negative {
background-color: rgba(255, 155, 155, .5);
}

Expand Down
6 changes: 6 additions & 0 deletions assets/scss/bewelcome.scss
Original file line number Diff line number Diff line change
Expand Up @@ -610,3 +610,9 @@ $avatar-sizes: 30, 50, 75, 100, 150, 200, 500;
.u\:hidden\! {
display: none !important;
}

.avatar-72 {
width: 72px;
height: 72px;
object-fit: cover;
}
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@
"phpstan/phpstan-doctrine": "*",
"phpstan/phpstan-mockery": "^2.0",
"phpstan/phpstan-symfony": "*",
"phpunit/php-file-iterator": "^6",
"phpunit/phpunit": "^12",
"rector/rector": "^2.1",
"squizlabs/php_codesniffer": "^3.5",
Expand Down Expand Up @@ -150,7 +149,7 @@
"config": {
"sort-packages": true,
"platform": {
"php": "8.4"
"php": "8.4.13"
},
"allow-plugins": {
"infection/extension-installer": true,
Expand Down
Loading
Loading