# Entwickler-Checkliste

Letzte Aktualisierung: 2026-04-01

## Ziel

Kurze Definition of Done vor Merge.

## Vor dem Coden

- [ ] Betroffene Schichten klar (`Repository`, `Service`, `pages`, `templates`, `web/js`)
- [ ] Permission- und Tenant-Scope-Auswirkungen geklärt
- [ ] Relevante bestehende Partials/Helper geprüft

## Beim Implementieren

- [ ] Kein SQL in `*.phtml`
- [ ] SQL nur in `core/Repository/**`
- [ ] Business-Logik nur in `core/Service/**`
- [ ] Neue UI-Texte über `t('...')`
- [ ] Listen-Filter konsistent: IDs/Enums als Select/MultiSelect statt Freitext
- [ ] Für persistierte Status/Outcomes nur zentrale Taxonomien aus `core/Domain/Taxonomy/*` nutzen (keine lokalen String-Literale)
- [ ] Für neue/angepasste Liste: `filter-schema.php` angelegt/aktualisiert
- [ ] File-Uploads über `templates/partials/app-file-upload.phtml` (kein inline File-Upload-Markup) — `$fileUpload`-Array mit `name`, `accept`, `hint`, optional `currentSrc`/`deleteAction`
- [ ] Listen-Titlebar über `templates/partials/app-list-titlebar.phtml` (kein inline `<div class="app-list-titlebar">`)
- [ ] Listen-Tabs über `templates/partials/app-list-tabs.phtml` (kein inline `<div class="app-list-tabs">`)
- [ ] Purge-Titlebar-Action über `templates/partials/app-list-purge-action.phtml` (kein inline Purge-Form/Button)
- [ ] Detail-Formulare mit Standardverhalten markieren (`data-standard-detail-form="1"`)
- [ ] Detail-Titlebar nutzt Unsaved/Shortcut-Contract (`data-detail-unsaved-message`, `data-detail-save-primary`)
- [ ] Detail-Aktionen nutzen Action-Policy-Contract (`data-detail-action-policy`, `data-detail-action-kind`, `data-detail-confirm-message`) statt inline `confirm(...)`
- [ ] Detail-Validierungsfehler über `templates/partials/app-details-validation-summary.phtml` (kein inline Error-Loop)
- [ ] Für Detail-Validation-Summary strukturierte Feldfehler setzen (`$validationSummaryErrors = $errorBag->toArray()`)
- [ ] Toolbar über `renderGridFilterToolbar(...)`, kein manuelles Filter-HTML
- [ ] Listen über `initStandardListPage(...)` initialisiert (kein direkter `createServerGrid(...)`-Standardpfad)
- [ ] Für Listen `searchToolbarFilterSchema` + `drawerToolbarFilterSchema` + `filterChipMeta` aus `index().php` bereitgestellt
- [ ] Keine direkten `gridFiltersFromSchema(...)`- oder `initListFilterExperience(...)`-Aufrufe in Listen-Templates
- [ ] Bei Filter-Drawer-Seiten: `filters.mode: 'drawer'` im Wrapper gesetzt
- [ ] Bei Search-only-Listen: kein Drawer-Markup, Quick Search bleibt live
- [ ] Aktive Filter-Chips vorhanden (`data-active-filter-chips`) und Remove/Clear funktionieren
- [ ] Kein Legacy-Toggle in Listen (`data-toolbar-toggle`, `data-filter-overflow`, `data-filter-toggle`)
- [ ] Falls Save-Filter vorhanden: gespeicherter Zustand stammt aus Applied State (`gridConfig.baseUrl()`)
- [ ] Keine inline `<script type="module">`-Blöcke in `pages/**`/`templates/**`; Seitenlogik liegt in `web/js/pages/*`
- [ ] Für Seitenmodule ist Config über `<script type="application/json" id="page-config-...">` bereitgestellt
- [ ] JS-`src` in `pages/**`/`templates/**` nur über `assetVersion('...js...')`
- [ ] Runtime-Komponenten folgen Contract `init(root, config) -> { destroy() }`
- [ ] Runtime-Komponenten enthalten keinen eigenen `DOMContentLoaded`-/Sideeffect-Auto-Init
- [ ] Listener/Timer/Observer aus Runtime-Komponenten werden in `destroy()` sauber abgebaut
- [ ] Runtime-Konfiguration liegt in `page-config-app-components` (default) bzw. `page-config-login-components` (login) und wird über `readPageConfig(...)` gelesen
- [ ] Host-gebundene Runtime-Komponenten haben explizite `data-app-component`-Hosts im Markup
- [ ] Global-Utilities ohne natürliches Host-Element sind explizit runtime-global registriert (`scope: 'global'`)
- [ ] Service-Auflösung in `pages/**`/`templates/**`/`core/Support/**` nur via `app(Foo::class)`
- [ ] Keine `new ...Factory()` in `core/Service/**` außerhalb `*Factory.php`
- [ ] Keine direkten `new ...Service()`, `new ...Gateway()`, `new ...Repository()` in `core/Service/**` außerhalb `*Factory.php`

## Sicherheits- und Scope-Checks

- [ ] Rechte serverseitig geprüft
- [ ] Tenant-Scope serverseitig geprüft
- [ ] Bei RBAC-Änderungen Playbook angewendet (`/docs/howto-rbac-permissions-playbook.md`)
- [ ] Schreibaktionen mit POST + CSRF abgesichert
- [ ] Confirm-Dialoge über Datencontract (`data-confirm-message` / `data-detail-confirm-message`), keine inline `confirm(...)`
- [ ] Keine sensiblen Daten in Fehlern/Logs
- [ ] Für `pages/**/data().php`: GET-only + Query-Normalisierung (`gridRequireGetRequest`, `gridParseFilters`)
- [ ] Für `pages/**/data().php`: Filter über `gridParseFilters(...)` statt Inline-Parsing
- [ ] Für `pages/api/v1/**`: kein `ApiResponse::readJsonBody(...)`, Input nur über `requestInput()`
- [ ] Bei MultiSelect-Filtern: CSV-Query (`foo=a,b`) + serverseitige `IN`-Filter mit Limit
- [ ] Keine Listen-Alias-Keys (`*_id` neben `*_ids`) neu einführen
- [ ] Input nur über `requestInput()` und Validation über `FormErrors` (`/docs/howto-request-input-validation.md`)

## Qualitätschecks

- [ ] `bin/qa-required.sh` (QG-001/002/003/006/008/009, blocking)
- [ ] optional: `bin/qa-extended.sh` (inkl. QG-004/005/007 als non-blocking Signale)
- [ ] `docker compose exec php vendor/bin/phpunit` zweimal hintereinander in identischer Umgebung (beide Runs gruen)
- [ ] `docker compose exec php vendor/bin/phpstan analyse -c phpstan.neon --no-progress`
- [ ] `docker compose exec php vendor/bin/phpunit tests/Architecture/CoreStarterkitContractTest.php`
- [ ] bei Modul-/Manifest-/Routen-Änderungen: `APP_ENABLED_MODULES='' php bin/console module:sync`
- [ ] bei Modul-/Manifest-/Routen-Änderungen: `APP_ENABLED_MODULES='addressbook,bookmarks' php bin/console module:sync`
- [ ] Runtime-Sync erzeugt Step-Summary (`migrate`, `permissions-sync`, `build`, `assets-sync`) und `vendor_warnings_ignored=<n>`
- [ ] Keine First-party CLI-Warnings/Notices/Deprecations im Runtime-Sync (Vendor-Warnings sind nur temporaer toleriert und müssen gezaehlt sichtbar sein)
- [ ] bei Docs-/Skills-Änderungen: `docker compose exec php vendor/bin/phpunit tests/Architecture/CodexSkillReferenceContractTest.php`
- [ ] `bin/docs-link-check.sh` (QG-008, scannt standardmäßig ohne `.agents/runs/**`)
- [ ] `bin/docs-drift-check.sh` (QG-008-Zusatz, blockt Legacy-Referenzen + fehlenden Setup-Vertrag)
- [ ] `bin/codex-skills-sync.sh --check` (QG-009)
- [ ] bei JS-Änderungen: Browser-Smoke mit DevTools-Console (keine JS-Errors)
- [ ] bei API-Änderungen: `docs/openapi.yaml` aktualisiert
- [ ] Struktur-Gates geprüft (`rg` für Instanziierungsregeln)
- [ ] `pages/**`: kein `new ...Service|Gateway|Repository`, kein `DB::`
- [ ] `pages/admin/**`: keine `app(...Factory::class)->create...`-Ketten
- [ ] `pages/admin/**`: keine `Factory::class`-Referenzen

## Manuelle Smoke-Tests

- [ ] Login/Logout
- [ ] betroffene Admin-Liste + Filter
- [ ] betroffene Edit-Seite + Save
- [ ] relevante Tenant-Sicht
- [ ] ggf. Global Search / API-Flow
- [ ] bei optionalen Modulen: Smoke-Test `APP_ENABLED_MODULES` on/off (keine toten Links, keine Notices/Warnungen)

## Abschluss

- [ ] Doku aktualisiert (`README.md`, `docs/*`)
- [ ] keine toten Imports/ungenutzten Helfer
- [ ] Änderung in 1-2 Minuten nachvollziehbar

## Vertiefung

- `/docs/reference-core-standards.md`
- `/docs/explanation-sicherheitsmodell.md`
- `/docs/howto-rbac-permissions-playbook.md`
- `/docs/howto-dokumentation-erweitern.md`
