# WOMS „Weißes Design“ P0: Vorbereitung – Umsetzungsplan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Nach P0 baut und testet der Worktree sauber, und der tote Code ist weg. Ein Screenshot- und Prüfwerkzeug erzeugt für jede Ansicht Bilder und meldet Design-Verstöße. Dabei wird kein Produktionssystem berührt. **Architecture:** - `node_modules` wird aus Git genommen, danach läuft im Worktree ein echtes `npm ci`. - Das Werkzeug liegt außerhalb des Repos unter `$W`. - Es baut die App mit **Vite** gegen erfundene Test-Hosts (`https://ticket.test`, `https://portal.test`). Vite nutzt intern esbuild und liefert dasselbe CSS-Bündel und dieselbe Reihenfolge wie der Produktions-Build. Ein eigenes esbuild-Bündel müsste Vite-Env und JSX-Einstellungen nachbauen. Die Spec nennt „esbuild-Bündel“, gleichwertig ist dieser Weg. - Playwright beantwortet alle Anfragen aus Testdaten und blockiert jeden anderen Host. **Tech Stack:** - Vite 5 - playwright-core 1.62.1 aus `/root/.npm/_npx/db89d7302a373f10/node_modules/playwright-core`, Laden per `createRequire` - Browser `/root/.cache/ms-playwright/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell` - Node 24 **Spec:** `docs/superpowers/specs/2026-10-04-weisses-design-design.md`, Paket P0 und „Abnahme“. Globale Regeln stehen in `2026-10-05-weisses-design-00-uebersicht.md`. Die Befehlsregeln dort (`env.sh`, `check.sh`, `run-browser.sh`) entstehen in diesem Paket. ## Global Constraints Es gelten die Regeln der Übersicht. Für P0 kommt hinzu: - Das Werkzeug ändert weder `package.json` noch die Lock-Datei noch `tests/` im Repo. - Kein Request darf einen echten Host erreichen. Jeder nicht nachgebildete Request wird abgebrochen und als Verstoß `external-request` gemeldet. - Für die Bilder blendet das Werkzeug die Schrift Inter ein, damit die Breiten realistisch sind. Quelle: `/home/webklar/apps/webklar.com/dev-relaunch/public/fonts/inter-latin.woff2` und `inter-latin-ext.woff2`, gesetzt per `addInitScript` als `--font`. Am Repo ändert sich dadurch nichts. ## Review Focus P0 liefert die Testdaten und Prüfungen für die Übersichtspunkte 1 bis 5: - Altdaten (`altdaten`) - lange Inhalte (`altdaten`, Prüfung `overflow-x`) - Teilausfall (Szenario `fehler`) - Tastatur (Schritte über `steps`) - viele Einträge (`viel`) --- ### Task 1: Worktree lauffähig machen **Files:** - Modify (Git-Index): `node_modules/**` wird aus Git genommen, nicht gelöscht - Read: `.gitignore` (muss `node_modules` enthalten) **Interfaces:** - Produces: Im Worktree laufen `npm test` und `npm run build` ohne Symlink. - [ ] **Step 1: Startprüfung** Run: `cd /home/webklar/apps/ticket.webklar.com/dev-design && git fetch origin && git log --oneline origin/test..feature/terminplaner-tp2 -- src/pages/LeadsPage.jsx src/hooks/useLeads.js && git rebase origin/test && git status --short | grep -v '^ D node_modules' ; grep -n '^node_modules' .gitignore` Expected: - Die `git log`-Zeile für TP2 gibt nichts aus. Gibt sie etwas aus: stoppen und den Eigentümer fragen. - Der Rebase läuft ohne Konflikt. - Vor dem `grep` auf `.gitignore` erscheint keine Zeile. - Danach erscheint eine Zeile `node_modules` oder `node_modules/`. - [ ] **Step 2: `node_modules` aus dem Git-Index nehmen** Run: `git rm -r -q --cached node_modules && git commit -q -m "node_modules nicht mehr im Repo (Deploy installiert ohnehin neu)" -m "Co-Authored-By: Claude Opus 5.5 " && git show --stat HEAD | tail -1` Expected: rund 8890 Löschungen. - [ ] **Step 3: Sauber installieren** Run: `rm -rf node_modules && systemd-run --scope -q -p MemoryMax=1500M -p MemorySwapMax=0 npm ci --no-audit --no-fund && git status --short` Expected: Exit 0 und keine Zeile aus `git status`. - [ ] **Step 4: Ausgangswerte festhalten** Run: `cd /home/webklar/apps/ticket.webklar.com/dev-design && T=/root/reports/ticketsystem-design-2026-10-04/werkzeug/tmp && mkdir -p "$T" && npm test > "$T/p0-test.log" 2>&1; echo "exit $?"; grep -E "^# (pass|fail)" "$T/p0-test.log"; systemd-run --scope -q -p MemoryMax=1500M -p MemorySwapMax=0 env NODE_OPTIONS=--max-old-space-size=1200 npm run build 2>&1 | tail -6` Task 1 und 2 laufen vor `env.sh` (kommt in Task 3), deshalb der feste Ordner `$T`. Expected: `exit 0`, `# pass 312`, `# fail 0` und ein Build ohne Fehler. Testzahl und Bundle-Größen (JS, CSS) kommen in die Commit-Nachricht von Task 2. ### Task 2: Toten Code löschen **Files:** - Delete: `src/pages/LeadsPage.jsx`, `src/hooks/useLeads.js` - Keep: `src/lib/leads.js`, die Route `/leads` mit Umleitung in `src/App.jsx`, den pausierten Menüpunkt in `src/lib/navLinks.js` - [ ] **Step 1: Unbenutzt beweisen** Run: `cd /home/webklar/apps/ticket.webklar.com/dev-design && rg -n "LeadsPage|useLeads" src tests` Expected: Treffer nur in den beiden Dateien selbst. - [ ] **Step 2: Löschen und prüfen** Run: `cd /home/webklar/apps/ticket.webklar.com/dev-design && T=/root/reports/ticketsystem-design-2026-10-04/werkzeug/tmp && git rm -q src/pages/LeadsPage.jsx src/hooks/useLeads.js && npm test > "$T/p0-test.log" 2>&1 && systemd-run --scope -q -p MemoryMax=1500M -p MemorySwapMax=0 env NODE_OPTIONS=--max-old-space-size=1200 npm run build > "$T/p0-build.log" 2>&1 && echo OK` Expected: `OK`. - [ ] **Step 3: Commit** ```bash git commit -q -m "LeadsPage und useLeads entfernt (seit Lead-Pause unbenutzt)" -m "Ausgangswerte: , JS B, CSS B" -m "Co-Authored-By: Claude Opus 5.5 " ``` ### Task 3: Werkzeug – Umgebung, Build, Test-Backend **Files** (alle unter `$W=/root/reports/ticketsystem-design-2026-10-04/werkzeug/`): - Create: `env.sh`, `check.sh`, `run-browser.sh`, `build.sh` - Create: `backend.mjs`, `appwrite-query.mjs`, `endpunkte.md`, `endpunkte.mjs` - Create: `fixtures/standard.mjs`, `fixtures/altdaten.mjs`, `fixtures/viel.mjs` - Create: `README.md` (Bedienung in zehn Zeilen) - Test: `selftest.mjs` **Interfaces:** - Produces: - **`env.sh`** setzt `WT`, `W`, `TMPDIR` (Scratchpad, wenn als `$CLAUDE_SCRATCHPAD` bekannt, sonst `$W/tmp`), `set -o pipefail`, `cd "$WT"`. - **`check.sh [--nur-tests|--nur-build]`** - `npm test` schreibt nach `$TMPDIR/test.log`. Ausgegeben werden die Zeilen `# pass`/`# fail`. - Der Build läuft mit `NODE_OPTIONS=--max-old-space-size=1200` unter `systemd-run` (1500M) und schreibt nach `$TMPDIR/build.log`. Ausgegeben werden die Größen der `.js`- und `.css`-Dateien. - Exit ist 0 nur, wenn beide Teile Exit 0 hatten. - **`run-browser.sh [args]`** startet `node` unter `systemd-run --scope -q -p MemoryMax=1500M -p MemorySwapMax=0`. - **`build.sh `** - Baut nach `$W/dist//` mit `VITE_APPWRITE_ENDPOINT=https://ticket.test/v1`, `VITE_APPWRITE_PROJECT_ID=test` und `VITE_PROJECT_ADMIN_URL=https://portal.test`. - Prozess-Variablen haben in Vite Vorrang vor `.env.production`. - Dazu `--outDir` und dieselbe Speichergrenze wie `check.sh`. - **`createBackend({ fixtures, scenario })`** aus `backend.mjs`. - `scenario` ist `'angemeldet' | 'abgemeldet' | 'mfa' | 'fehler'`. - Rückgabe: `{ handle(route, request), calls: [{ method, url, body }], violations: [] }`. - **`applyQueries(documents, queries) → { total, documents }`** aus `appwrite-query.mjs`. - [ ] **Step 0: Umgebungs-Skripte anlegen** `env.sh`, `check.sh`, `run-browser.sh` und `build.sh` werden laut Interfaces geschrieben. `chmod +x` gilt für alle außer `env.sh`. Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && echo "$WT $W $TMPDIR" && "$W/check.sh" --nur-tests; echo "exit $?"` Expected: drei Pfade, die Zeilen `# pass`/`# fail` und `exit 0`. - [ ] **Step 1: Endpunkt-Inventar anlegen** `endpunkte.mjs` sucht in `$WT/src`: - `fetch(` mit Basis-URL - `VITE_PROJECT_ADMIN_URL`-Basen mit Pfad - `/api/…`-Literale - `databases.*`-Aufrufe mit Collection-Konstanten - `account.*`- und `client.call`-Aufrufe Es gibt eine Liste `{ datei, methode, muster }` aus. Daraus entsteht von Hand `endpunkte.md`, eine Tabelle mit diesen Spalten: - Quelldatei - Endpunkt-Muster - Fixture-Schlüssel - Antwortform (Felder, die der Code liest) - Verhalten im Szenario `fehler` `fehler` ist so festgelegt: | Anfrage | Antwort im Szenario `fehler` | |---|---| | `GET …/collections/worksheets/documents` | 500 | | `GET …/collections/employees/documents` | 500 | | `GET https://portal.test/api/admin/employees` | 500 | | `GET https://portal.test/api/team/*` | 500 | | `GET …/collections/workorders/documents` mit `limit(1)` (Zähler) | 500 | | alle anderen | normal | - [ ] **Step 2: Failing Selbsttest schreiben** `selftest.mjs` prüft mit `node:assert`: ```js const { total, documents } = applyQueries(docs, ['equal("status", ["Open","Occupied"])', 'notEqual("type", ["Akquise"])', 'orderDesc("$createdAt")', 'limit(2)']) // total = Treffer vor limit, documents.length === 2, neueste zuerst applyQueries(docs, ['equal("woid", ["100131"])']) // findet woid als Zahl oder Text applyQueries(docs, ['offset(2)', 'limit(1)']) // drittes Dokument applyQueries(docs, ['cursorAfter("id2")', 'limit(1)']) applyQueries(docs, ['select(["woid","totalTime"])']) // nur diese Felder plus $id ``` Weitere Fälle: - Ein Request an `https://example.com/x` wird abgebrochen und steht als `{ kind: 'external-request', url }` in `violations`. - `GET https://ticket.test/v1/account` liefert 200 im Szenario `angemeldet`, 401 in `abgemeldet` und in `mfa` (nach dem Login) 401 mit `type: 'user_more_factors_required'`. - Im Szenario `fehler` liefert `GET …/collections/worksheets/documents` 500. - Jedes Muster aus `endpunkte.md` liefert im Szenario `angemeldet` eine Antwort, nie eine `external-request`-Verletzung. Das Muster wird aus der Tabelle gelesen. - [ ] **Step 3: Selbsttest laufen lassen** Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && cd "$W" && node selftest.mjs` Expected: FAIL (Module fehlen). - [ ] **Step 4: `applyQueries` implementieren** Unterstützt werden: - Filter: `equal`, `notEqual`, `greaterThan`, `lessThan`, `contains`, `search` (Teilstring, ohne Groß-/Kleinschreibung), `isNull`, `isNotNull` - Sortierung: `orderAsc`, `orderDesc` - Seiten: `limit` (Standard 25), `offset`, `cursorAfter` - Felder: `select` Ein unbekannter Query-Typ ergibt den Verstoß `unknown-query` und wird ignoriert. - [ ] **Step 5: Backend implementieren** | Anfrage | Antwort | |---|---| | `https://ticket.test/v1/...` | Appwrite-Nachbau | | `https://ticket.test/api/...` | gleichseitige APIs (Serverpuls u. a.) laut Inventar | | sonstige `https://ticket.test/...` | Datei aus `$W/dist//`, unbekannte Pfade bekommen `index.html` | | `https://portal.test/...` | Portal- und Admin-API laut Inventar. Der Chat-Composer (`loadChatComposer.js`) bekommt eine leere JS- und CSS-Datei, bis P6 den echten Baustein einbindet. | | andere URLs | `route.abort()` und Verstoß `external-request` | Schreibende Requests ändern die Testdaten im Speicher. Jeder Request landet in `calls`. - [ ] **Step 6: Testdaten anlegen** **`fixtures/standard.mjs`** - Angemeldet ist `kenso-admin` mit Labels `['admin']`, Name „Kenso Grimm“. - 4 Mitarbeiter: KNSO, ANDJ, JUSN, NIRE. - 9 erfundene Kunden aus `quelle/data.js`: - Stufen fest, lead und archiv - einer mit `customerStatus: 'lost'` (Abgesagt) - einer mit `isImportant: true` - 12 Tickets mit allen Werten aus `TICKET_STATUSES` plus `In Kontakt`, `Zugesagt`, `Abgesagt` und `Assigned`. Arten Webpage, Migration, Project und Akquise. - **Ticket 100131 ist fest:** - Kunde „Physiotherapie am Markt“ - `serviceType: 'On Site'` - Arbeitsblätter: - Git-Push ohne Zeit (`serviceType: 'GIT'`, `employeeName: 'knso'`, `totalTime: 0`) - Git-Push mit 45 min (`employeeName: 'ANDJ'`) - Kommentar (`isComment: true`) - Arbeitszeit von `andj` über 60 min mit `supporters: '[{"userId":"kenso-admin","task":"DNS geprüft"}]'` - Arbeitszeit von `kenso-admin` über 90 min mit `countsToPlan: true` - Projekt `WEBklar/physio-am-markt` - 10 `websiteProjects` aller Typen, darunter eine Vorlage und ein archiviertes. - Rechtliches: Dokumente, Versionen und Anbieter. - Antworten für Finanzen, Serverpuls (mit Zeitreihen für die Diagramme), Gutschriften, Abo-Angebote, E-Mail-Speicher und Chat-Liste, jeweils laut Inventar. **`fixtures/altdaten.mjs`** - Ticket `startDate: '2.5.2026'` - Ticket `deadline: '31.02.2026'` - Ticket `startDate: '2026-10-07T09:00:00.000Z'` - Ticket ohne `woid` - Ticket mit `status: ''`, Ticket mit `status: 'Completed'` - Ticket ohne `customerName` - Betreff mit 120 Zeichen - Kundenname mit 60 Zeichen **`fixtures/viel.mjs`** - Ticket 100131 mit 300 Arbeitsblättern - 250 Tickets, deren Betreff „Test“ enthält - [ ] **Step 7: Selbsttest grün** Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && cd "$W" && node selftest.mjs` Expected: `selftest ok`. - [ ] **Step 8: Probebau** Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" vorher && ls "$W/dist/vorher/index.html" && rg -n "https://(ticket|project)\.webklar\.com" "$W"/dist/vorher/assets/*.js | cut -c1-160` Expected: `index.html` ist vorhanden. Treffer der echten Hosts sind nur Texte oder Links für Menschen, keine API-Basen. Jeden Treffer prüfst du einzeln. - [ ] **Step 9: `check.sh` vollständig prüfen** Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh"; echo "exit $?"` Expected: Die Zeilen `# pass 312` und `# fail 0`, die Bundle-Größen und `exit 0`. ### Task 4: Werkzeug – Ansichten, Prüfungen, Vorher-Bilder **Files** (unter `$W`): - Create: `views.mjs`, `checks.mjs`, `gates.mjs`, `shots.mjs`, `shots.sh` **Interfaces:** - Consumes: Backend, Testdaten und `build.sh` aus Task 3. - Produces: Formate von `VIEWS` und `GATES` wie in der Übersicht. Zusätzlich: - `runChecks(page) → Promise>` aus `checks.mjs`. - `submitReachable(page)` aus `views.mjs` sucht den Absende-Knopf im offenen Dialog (`[type=submit]`, sonst den letzten Primärknopf), ruft `scrollIntoView` auf und wirft, wenn er danach nicht vollständig im Viewport liegt. - `shots.sh [--only ids] [--gate p] [--static ]` - Eine unbekannte ID in `--only` endet mit Exit 2. - Einträge in `views` eines Gates dürfen mit `*` enden (Präfix). - `--static` lädt ein HTML-Verzeichnis ohne Backend. So prüft man das Werkzeug am Prototyp. - [ ] **Step 1: Ansichten anlegen** | ID | Pfad / Schritte | |---|---| | `login` | `/login`, Szenario `abgemeldet` | | `login-code` | Szenario `mfa`: Formular absenden, warten auf `#mfa-code` | | `login-code-senden` | Szenario `mfa`: Formular absenden, Seite neu laden, warten auf den Knopf „Code senden“ | | `tickets` | `/tickets` | | `tickets-suche` | Testdaten `viel`, `/tickets`, Suche „Test“ (heute über „Anwenden“) | | `ticket` | `/tickets?woid=100131` (heute aufgeklappte Karte) | | `ticket-arbeitsblaetter` | wie `ticket`, dann Reiter „Arbeitsblätter“ | | `ticket-altdaten` | Testdaten `altdaten`, `/tickets` | | `fehler-tickets` | Szenario `fehler`, `/tickets` | | `dlg-ticket-neu` | Dialog „Neues Ticket“ | | `dlg-arbeitsblatt` | Dialog „Arbeitsblatt hinzufügen“ | | `dlg-zeiten` | Dialog „Zeit nachtragen“ | | `dlg-unterstuetzung` | Dialog „Ich habe unterstützt“ | | `planboard` | `/planboard` | | `kunden` | `/customers` | | `kunde-` | `/customers/?tab=` mit den Reiter-IDs aus der Übersicht | | `chat` | `/customers/chat` | | `gutschriften` | `/customers/gutschriften` | | `angebote` | `/customers/angebote` | | `speicher` | `/customers/email-speicher` | | `projekte` | `/projects` | | `dlg-projekt-neu` | Dialog „Neues Projekt“ | | `dlg-projekt-bearbeiten` | Dialog „Projekt bearbeiten“ | | `dlg-projekt-art` | Art-Frage in `TicketProjects` | | `zuordnen` | `/projects/zuordnen/` | | `services` | `/services` | | `analyse-zustimmung` | Zustimmungsablauf in `AnalyseRow` | | `finanzen` | `/finance` | | `dlg-rechnung` | Rechnungsdialog | | `dlg-zuordnen-zahlung` | `AssignDialog` | | `finanzen-einstellungen` | `/finance/einstellungen` | | `rechtliches` | `/rechtliches` | | `rechtliches-anbieter` | `/rechtliches`, zweiter Reiter | | `dlg-vertrag` | Vertrags-Formular | | `dlg-version` | Versions-Formular | | `admin` | `/admin` | | `admin-neu` | Mitarbeiter-Formular offen | | `serverpuls` | `/serverpuls`, warten bis `canvas` gezeichnet ist | Alle `dlg-*`-Ansichten: - haben `viewports: [[1440, 900], [390, 844], [1440, 700], [390, 700]]` - rufen am Ende `submitReachable(page)` auf Jede Ansicht wartet auf `document.fonts.ready` und 300 ms. Dazu kommt `reducedMotion: 'reduce'`. - [ ] **Step 2: Prüfungen schreiben** `checks.mjs` läuft im Browser über alle sichtbaren Elemente: | Prüfung | Regel | |---|---| | `dark-surface` | Verstoß bei eigenem, deckendem Hintergrund mit relativer Helligkeit < 0,5. Für `background-image` mit `linear-gradient` zählt der Mittelwert der Farbstopps. Ausnahmen: `.btn-primary`, `.badge`, `.toast`, `.nav-dot`, `.day.today .dn`, `.sw`, `.code-input`, `[class*="lightbox"]`, `[class*="camera"]` und `canvas`. | | `low-contrast` | Verstoß bei Textkontrast < 3:1 gegen den nächsten deckenden Hintergrund. Gradienten zählen wie oben. Ausnahmen: `disabled`, `aria-disabled` und Platzhalter. | | `icon-svg` | Verstoß bei einem sichtbaren `svg` außerhalb von `.brand`, `.login-box > svg` und `.woms-chat .chat-composer *` | | `overflow-x` | Verstoß bei `scrollWidth > innerWidth + 1` | | `console-error` | Verstoß bei `pageerror` oder `console.error`. Im Szenario `fehler` sind die erwarteten 500-Meldungen ausgenommen. | | `external-request`, `unknown-query` | Verstöße aus dem Backend | | `step-failed` | eine Ausnahme in `steps` | - [ ] **Step 3: Gates festlegen** ```js export const GATES = { p0: { checks: ['external-request', 'console-error', 'step-failed'], views: '*' }, p1: { checks: ['external-request', 'console-error', 'step-failed', 'dark-surface', 'low-contrast', 'overflow-x'], views: '*' }, } ``` Spätere Pakete ergänzen nach dem Schema der Übersicht. - [ ] **Step 4: Läufer schreiben** `shots.mjs`: - Je Ansicht und Viewport ein Kontext. Unter 1000 px Breite gilt `isMobile`, `hasTouch` und `deviceScaleFactor: 2`. - Dazu `locale: 'de-DE'` und `timezoneId: 'Europe/Berlin'`. - Ausgabe: `/x/.png` und `/report.json` mit `{ view, viewport, issues[] }`. - Der Exit-Code richtet sich nach `--gate`. `shots.sh` startet `shots.mjs` über `run-browser.sh`. - [ ] **Step 5: Vorher-Bilder** Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/shots.sh" vorher "$W/shots/vorher" --gate p0` Expected: - Exit 0. - Für jede Ansicht ein Bild je Viewport. - Kein `external-request`. - `dark-surface` und `low-contrast` dürfen massenhaft auftreten. Sie sind nicht gate-relevant. - [ ] **Step 6: Gegenprobe am Prototyp** Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/shots.sh" prototyp "$W/shots/prototyp" --static /root/reports/ticketsystem-design-2026-10-04/quelle --only tickets` Expected: - 0 Verstöße für `dark-surface` und `low-contrast`. - Zusätzlich hat `$W/shots/vorher/report.json` für `tickets` mindestens einen `dark-surface`-Treffer. Das beweist, dass die Prüfung anschlägt. ### Task 5: Abschluss P0 - [ ] **Step 1: Tag** Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git fetch origin && git rebase origin/test && "$W/check.sh" && git tag design-p0` Expected: Exit 0 und das Tag ist gesetzt. P0 wird nicht gepusht, sondern geht mit P1 live. - [ ] **Step 2: Code-Review (sonnet)** Geprüft werden `603d08c..design-p0` und das Werkzeug (`$W`), mit Blick auf zwei Fragen: - Kann ein Request einen echten Host erreichen? - Melden die Prüfungen falsch-negativ? Wichtige Befunde werden behoben. Ein späterer Commit bekommt das Tag `design-p0-fix1`. - [ ] **Step 3: Bericht an den Eigentümer** Der Bericht nennt die Ausgangswerte, den Ort der Vorher-Bilder und den Hinweis, dass P0 unsichtbar ist und mit P1 live geht.