# Entwickler-Checkliste

Letzte Aktualisierung: 2026-03-09

## 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 `lib/Repository/**`
- [ ] Business-Logik nur in `lib/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 `lib/Domain/Taxonomy/*` nutzen (keine lokalen String-Literale)
- [ ] Für neue/angepasste Liste: `filter-schema.php` angelegt/aktualisiert
- [ ] 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...')`
- [ ] Service-Auflösung in `pages/**`/`templates/**`/`lib/Support/**` nur via `app(Foo::class)`
- [ ] Keine `new ...Factory()` in `lib/Service/**` außerhalb `*Factory.php`
- [ ] Keine direkten `new ...Service()`, `new ...Gateway()`, `new ...Repository()` in `lib/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

- [ ] `docker compose exec php vendor/bin/phpunit`
- [ ] `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 Docs-/Skills-Aenderungen: `docker compose exec php vendor/bin/phpunit tests/Architecture/CodexSkillsContractTest.php`
- [ ] bei Docs-/Skills-Aenderungen: `bin/docs-link-check.sh` (scannt standardmaessig ohne `agent-system/runs/**`)
- [ ] bei Docs-/Skills-Aenderungen: `bin/codex-skills-sync.sh --check`
- [ ] 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

## Abschluss

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

## Vertiefung

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