# WOMS „Weißes Design“ – Übersicht der Umsetzungspläne > **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:** Das ganze Ticketsystem WOMS bekommt das freigegebene weiße Design (Akzent Schwarz, kaum Rahmen, kaum Icons). Ein Ticket öffnet künftig als eigene Seite. Die Umsetzung kommt in elf einzeln auslieferbaren Paketen. **Architecture:** - P1 legt Tokens, Gerüst, Bausteine und eine Übergangsschicht (KOMPAT) in `global.css` an. Danach ist die App sofort weiß. - P2 bis P9 bauen je einen Bereich auf die neuen Bausteine um. - P10 räumt Altlasten und Icon-Bibliotheken ab. - Jedes Paket geht nur über ein Git-Tag live. **Tech Stack:** - React 18 - react-router-dom 6 - Vite 5 - Appwrite Web SDK 13 (Legacy-Query-Syntax) - reines CSS ohne Framework - Tests: `node --test` mit esbuild-Lader (`tests/unit/load.mjs`) - Screenshots: playwright-core 1.62.1 und chromium-headless-shell 1234 **Spec:** - `docs/superpowers/specs/2026-10-04-weisses-design-design.md`, verbindlich - Anhang `docs/superpowers/specs/2026-10-04-weisses-design-umbaukarte.md` - Prototyp: `/root/reports/ticketsystem-design-2026-10-04/quelle/` (`styles.css`, `app.js`) ## Pläne | Paket | Datei | Kurz | |---|---|---| | P0 | `2026-10-05-weisses-design-p0-vorbereitung.md` | Worktree lauffähig, Totcode, Screenshot-Werkzeug | | P1 | `2026-10-05-weisses-design-p1-fundament.md` | Tokens, KOMPAT, Gerüst, Menü, Bausteine, Dialog, Login, Pflicht-Fixes | | P2 | `2026-10-05-weisses-design-p2-tickets.md` | Ticketliste, Ticket-Seite, Arbeitsblätter, Ticket-Dialoge | | P3 | `2026-10-05-weisses-design-p3-planboard.md` | Planboard | | P4 | `2026-10-05-weisses-design-p4-kunden.md` | Kundenliste, Kundenseite | | P5 | `2026-10-05-weisses-design-p5-projekte.md` | Projekte, Zuordnen, Services, Projekt-Bausteine | | P6 | `2026-10-05-weisses-design-p6-kunden-unterseiten.md` | Chat, Gutschriften, Abo-Angebote, E-Mail-Speicher | | P7 | `2026-10-05-weisses-design-p7-finanzen.md` | Finanzen | | P8 | `2026-10-05-weisses-design-p8-rechtliches-admin.md` | Rechtliches, Admin | | P9 | `2026-10-05-weisses-design-p9-serverpuls.md` | Serverpuls | | P10 | `2026-10-05-weisses-design-p10-aufraeumen.md` | KOMPAT, Aliase, Icon-Pakete entfernen | ## Reihenfolge - Die Pakete werden strikt in der Reihenfolge P0 → P10 umgesetzt. - P0 geht nicht allein live, sondern zusammen mit P1. Das Tag `design-p1` enthält die P0-Commits. - An Paket N+1 wird erst gearbeitet, wenn `design-p` gesetzt ist. ## Global Constraints Diese Regeln gelten für jede Aufgabe aller Pakete. **Befehle und Prüfungen** - Jede Bash-Ausführung beginnt mit `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh`. Das Skript (aus P0) setzt: - `WT=/home/webklar/apps/ticket.webklar.com/dev-design` - `W=/root/reports/ticketsystem-design-2026-10-04/werkzeug` - `TMPDIR=$W/tmp` - `set -o pipefail` - `cd "$WT"` Temporäre Dateien liegen nie direkt in `/tmp`. Auch die ```bash-Commit-Blöcke der Pläne laufen in einem Shell-Aufruf, der zuerst `env.sh` lädt. - Tests und Build werden nur über `"$W/check.sh"` als Bedingung genutzt. Varianten: - `"$W/check.sh"` für Tests und Build - `"$W/check.sh" --nur-tests` - `"$W/check.sh" --nur-build` Das Skript gibt die Zeilen `# pass`/`# fail` und die Bundle-Größen aus und endet mit dem Exit-Code von `npm test` bzw. `npm run build`. Ein Tag wird nur mit `"$W/check.sh" && git tag design-p` gesetzt. Ketten wie `npm test | grep … && …` sind verboten, weil sie den Exit-Code von `grep` werten. - Browser-Skripte (Playwright, Pixelvergleich) laufen nie direkt mit `node`, sondern über die Hüllen `$W/shots.sh` oder `$W/run-browser.sh [args]`. Beide starten unter `systemd-run --scope -q -p MemoryMax=1500M -p MemorySwapMax=0`. - `rg` auf diesem Host ist Version 13.0.0 ohne PCRE2. Deshalb kein `rg -P`, keine Lookarounds. **CSS-Reihenfolge** - `src/main.jsx` importiert `App` vor `./styles/global.css`. Deshalb stehen alle von Komponenten importierten Stylesheets im Bundle **vor** `global.css`: `chat.css`, `serverpuls.css`, `admin-areas.css`, `pages/AdminPage.css` und `uplot.min.css`. - Bei gleicher Spezifität gewinnt `global.css`. Bereichs-CSS, das eine globale Regel überstimmen muss, braucht eine höhere Spezifität (z. B. `.page.admin-page`) und nie `!important`. **Parallele Arbeit an WOMS** - Auf `feature/terminplaner-tp2` arbeitet eine andere Sitzung weiter. Stand 2026-10-05: 5 Commits vor `test`, sie berühren u. a. `CustomerFunktionen.jsx`, `lib/funktionenView.js` und Rechtliches. - Vor dem Start jedes Pakets wird `git log --oneline origin/test..feature/terminplaner-tp2 -- ` geprüft. Gibt es Treffer, stoppt der Agent und fragt den Eigentümer. Er empfiehlt dabei, TP2 zuerst nach `test` zu bringen. **Paketstart und Abnahme-Reihenfolge** - Paketstart: - `git fetch origin` - `git merge-base --is-ancestor design-p HEAD`, sonst stoppen und melden - `git rebase origin/test` - `"$W/check.sh"` - Abnahme: erst `git fetch origin && git rebase origin/test`, dann Gate-Lauf, Screenshots und Review. So zeigen die Bilder den Stand, der getaggt wird. - Jede Übergabe nennt neben der Push-Zeile auch die Rücknahme: `git revert --no-commit design-p..design-p` als Commit auf `test`. Das gilt nur für das jüngste Paket. **Bausteine benutzen** - Jeder Knopf in einem `` außer dem Absenden-Knopf trägt `type="button"`. `Button` setzt keinen eigenen Standard-Typ, weil Bestandsformulare darauf bauen. - Abstände zwischen aufeinanderfolgenden Abschnitten regelt P1 global mit `.page section + section { margin-top: 36px }`. Pakete bauen dafür keine eigenen Regeln. Eigene Abstände sind nur innerhalb von Bereichs-Containern erlaubt, die keine `section` sind. - Klickbare Tabellenzeilen prüfen Klicks immer mit `isRowActivation(event)` aus `src/lib/rowClick.js` (entsteht in P2, Task 2). Kein Paket baut diese Regel neu. **Commit-Trailer** - In den Commit-Beispielen steht `Co-Authored-By: Claude Opus 5.5 `. Maßgeblich ist die Attribution, die die ausführende Sitzung vorgibt. **Arbeitsort und Git** - Gearbeitet wird nur in `/home/webklar/apps/ticket.webklar.com/dev-design` auf dem Branch `feature/design-weiss`. In `/home/webklar/apps/ticket.webklar.com/build/` (Live-Checkout) wird nie etwas geändert. - `git add` nur mit ausdrücklichen Pfaden, nie `-A` oder `.`. - Commit-Nachrichten auf Deutsch, letzte Zeile `Co-Authored-By: Claude Opus 5.5 `. - Niemals `git push`. Live bringt nur der Eigentümer mit `git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p^{commit}:refs/heads/test'`. - Vor dem Paketstart und vor dem Setzen des Tags: `git fetch origin && git rebase origin/test`. Das Tag `design-p` wird danach gesetzt und nie bewegt. Korrekturen nach dem Push: `design-p-fix`. - Nicht anfassen: - `.env*` (Latin-1) - das Portal-Repo - `src/lib/loadChatComposer.js` samt Test - die Container-Klassen in `src/components/chat/ChatThread.jsx` (`woms-chat`, `chat-messages`) - Appwrite-Schema und gespeicherte Werte **Speicher und Prozesse** - Speicherintensives (`npm ci`, `npm run build`, Playwright) läuft unter `systemd-run --scope -q -p MemoryMax=1500M -p MemorySwapMax=0`, ein Build zusätzlich mit `NODE_OPTIONS=--max-old-space-size=1200`. - Prozesse nur per PID beenden, nie mit `pkill -f` (beendet sonst die eigene Shell). **Tests und Screenshots** - Tests laufen ohne Produktionszugriff. Das Screenshot-Werkzeug blockiert jede Anfrage an einen echten Host. - Lauf: `npm test`, alle Tests grün. - Neue Lib-Funktionen bekommen Tests unter `tests/unit/.test.mjs` im Stil der bestehenden Tests (`node:test`, `assert/strict`, `loadModule`). **Gestaltung** - Akzent `#111111`. Pro Seitenkopf höchstens ein `.btn-primary`. - Breakpoints 1100 px (`.hide-md`) und 860 px (`.hide-sm`, Schublade, Bottom-Sheet). - Die Token-Werte stehen exakt in der Spec, Abschnitt „Design-Tokens“, und werden nicht neu erfunden. - Keine Icon-Komponenten in umgebauten Dateien. Handelnde Zeichen werden Text: „ד, „←“, „→“, „↑“, „↓“. - Keine neuen `style={{…}}` außer für Werte, die erst zur Laufzeit feststehen. - Kein Farbliteral in JSX. Farben nur über Tokens. Einzige Ausnahme: Canvas-Farben in `src/components/serverpuls/chartColors.js`. **Texte und Verhalten** - Texte bleiben wörtlich. Geändert wird nur, was die Spec unter „Texte“ und in den Bausteinen nennt. - Rechtstexte, Mail- und Chat-Vorlagen, Fehlermeldungen und der Wortlaut in `lib/analyseView.js` bleiben unverändert. - Verhalten bleibt, außer „Bewusste Änderungen am Verhalten“ der Spec. Alle `window.confirm`-Rückfragen, `data-testid`, Doppelklick-Sperren (`busyRef`, `lockRef`) und URL-Parameter bleiben. **Abschluss jedes Pakets** - Abnahme nach Spec „Abnahme“, Punkte 1 bis 7. - Code-Review mit dem Modell `sonnet`. Wichtige Befunde werden behoben, Kleinigkeiten notiert. ## Gemeinsame Schnittstellen aus P1 Diese Bausteine und Klassen sind ab P1 vorhanden. Spätere Pakete benutzen sie und erfinden keine eigenen. **Komponenten** - **`src/components/ui/Button.jsx`** - Signatur: `Button({ variant = 'default', size, as = 'button', className = '', ...props })`. - Varianten: `'default' | 'ghost'` → `btn`, `'primary'` → `btn btn-primary`, `'quiet'` → `btn btn-quiet`, `'danger'` → `link-danger`. - `size` wird ignoriert. - **`src/components/ui/Badge.jsx`** - Signatur: `Badge({ tone = 'muted', className = '', style, children })`. - Tonzuordnung: `muted|grey` → `''`, `info|blue` → `blue`, `ok|green` → `green`, `warn|orange` → `orange`, `danger|red` → `red`, dazu `violet` und `plain`. - Die Prop `dot` wird ignoriert. - **`src/components/ui/Card.jsx`** - Signatur: `Card({ title, actions, className, children })`. - Rendert `
`, mit Titel `

{title}{actions && {actions}}

`. - Ohne `title` und ohne `actions` entfällt das `h2`, die Kinder stehen direkt in `
`. - `pad`, `bodyStyle` und `style` werden ignoriert. - **`src/components/ui/PageHeader.jsx`** - Signatur: `PageHeader({ title, subtitle, actions, eyebrow, crumb, children })`. - `crumb` ist ein Array `[{ label: string, to?: string }]`, gerendert als `.crumb` mit „/“ in `--text-3` vor `.page-head`. - **`src/components/ui/Tabs.jsx`** - Signatur: `Tabs({ tabs: [{ id, label, count? }], active, onChange })`. - Rendert `.tabs`, `role="tablist"`, Buttons `.tab` mit `role="tab"`, `aria-selected`, Zähler `` bei `count != null`. - **`src/components/ui/EmptyState.jsx`**: `EmptyState({ title, hint, action })` → `.empty`. - **`src/components/ui/Field.jsx`**: `Field({ label, htmlFor, hint, children })` → `.field`, `.hint`. - **`src/components/ui/Stat.jsx`** - Signatur: `Stat({ label, value, sub, tone = '' })`, wobei `tone` `'' | 'warn' | 'neg'` ist. - Rendert `.stat` mit `.k` (Label), `.v {tone}` (Wert) und `.sub`. - Der Container `.stats` kommt vom Aufrufer. - **`src/components/ui/Kpi.jsx`** - Signatur: `Kpi({ label, value, sub, icon, accent })`. - Gibt `` zurück und ignoriert `icon`. - Wird in P7 gelöscht. - **`src/components/ui/StatusPill.jsx`** - `StatusPill({ status })` → `{statusLabel(status)}`. - `UrgentPill({ priority })` → `badge red` „Dringend“, nur wenn `isUrgent(priority)`. - **`src/components/ui/Dialog.jsx`** - Signatur: `Dialog({ title, onClose, busy = false, wide = false, as = 'div', onSubmit, footer, children })`. - Aufbau: Overlay `.overlay`, Box `.dialog` (`.dialog-wide`), Kopf `.dialog-head` mit `

` und ``, darunter `.dialog-body` und `.dialog-foot`. - Mit `as="form"` ist die Box ein `
`, die native Pflichtfeld-Prüfung bleibt aktiv. - Schließen: Esc nur bei `!e.defaultPrevented && !busy`. Ein Klick auf den Hintergrund schließt nie. - Fokus über `useDialogFocus`. - **`src/components/Logo.jsx`**: `Logo({ size = 24 })` mit dem SVG aus dem Prototyp (`app.js`, Konstante `LOGO`). - **`src/lib/ticketStatus.js`**: `statusTone(status) → '' | 'blue' | 'orange' | 'violet' | 'green' | 'plain'`. - **`src/components/serverpuls/chartColors.js`**: `export const CHART = { line: '#111111', fill: 'rgba(17,17,17,.06)', peak: '#8a93a3', axis: '#5e6678', grid: '#ebeef1', tick: '#d8dde3' }`. **CSS-Klassen in `global.css`** Prototyp-Namen, Werte aus `quelle/styles.css`: | Bereich | Klassen | |---|---| | Gerüst | `.shell(.menu-open)`, `.sidebar`, `.brand`, `.nav`, `.nav-item(.sub .active .paused)`, `.nav-count(.hot)`, `.nav-dot(.critical)`, `.sidebar-foot`, `.main`, `.mobile-bar`, `.scrim`, `.page` | | Seitenkopf | `.crumb`, `.page-head`, `.eyebrow`, `.sub`, `.actions`, `h2.section` | | Knöpfe | `.btn`, `.btn-primary`, `.btn-quiet`, `.btn-block` | | Links | `.link`, `.link-danger` | | Badges | `.badge(.blue .orange .violet .green .red .plain)` | | Reiter | `.tabs`, `.tab(.active)`, `.count` | | Formular | `.toolbar`, `.grow`, `.input`, `.select`, `.textarea`, `.search`, `.select-quiet`, `.check`, `.field`, `.hint`, `.field-row`, `.field-row-3`, `.code-input` | | Tabelle | `.table`, `tr.row`, `.right`, `.cell-title`, `.cell-sub`, `.id`, `.table-foot`, `.hide-md`, `.hide-sm` | | Zustände | `.empty`, `.loading`, `.msg(.err .ok .warn)`, `.toast` | | Werte | `.facts`, `.fact`, `.k`, `.v`, `.stats`, `.stat`, `.v.warn`, `.v.neg`, `.kv`, `.two-col`, `.prose` | | Bedienelemente | `.urgent-toggle(.on)`, `.sw`, `.list-row`, `.seg(button.on)` | | Dialog | `.overlay`, `.dialog`, `.dialog-wide`, `.dialog-head`, `.dialog-close`, `.dialog-body`, `.dialog-foot` | | Login | `.login`, `.login-box`, `.login-links`, `.login-foot` | | Hilfsklassen | `.num`, `.muted`, `.faint`, `.small`, `.xs`, `.strong`, `.nowrap` | | Zustände | `:disabled` (Deckkraft .55) und `[readonly]` (`--bg-subtle`) | `.ws*` kommt mit P2, `.week`/`.day`/`.appt` mit P3. **Screenshot-Werkzeug aus P0** Es liegt unter `$W`. | Befehl | Wirkung | |---|---| | `env.sh` | Umgebung, siehe oben | | `check.sh [--nur-tests\|--nur-build]` | Tests und/oder Build mit echtem Exit-Code | | `build.sh $WT ` | Vite-Build mit Test-Endpunkten nach `$W/dist/` | | `shots.sh [--only id,id] [--gate p]` | Screenshots plus Prüfbericht `/report.json` | | `run-browser.sh [args]` | jedes andere Browser-Skript unter Speichergrenze | Zu `shots.sh`: - Eine unbekannte ID in `--only` bricht mit Exit 2 ab. - `--gate p` endet bei Verstößen mit Exit 1. **`$W/views.mjs`** - Format: `VIEWS: Array<{ id, path, fixtures = 'standard', scenario = 'angemeldet', viewports?: Array<[w, h]>, steps?: async (page, ctx) => void }>`. - `ctx.backend` gibt Zugriff auf `calls` (alle beantworteten Requests) und `violations`. - Ein Fehler in `steps` erzeugt den Verstoß `step-failed`. - Standard-Viewports sind `[[1440, 900], [390, 844]]`. **`$W/gates.mjs`** - Format: `GATES: { p: { checks: string[], views: '*' | string[], scoped?: { [check]: { views: string[], ignore?: string[] } } } }`. - `checks` gilt für `views`. Einträge in `views` dürfen mit `*` enden und gelten dann als Präfix (z. B. `'tickets-*'`). Einträge mit führendem `!` schließen aus (nach den Einschlüssen). Ein Muster, das keine Ansicht trifft, eine leere Liste oder ein unbekannter Prüfname beenden den Lauf mit Exit 2. - Immer aktiv sind zusätzlich `unhandled-request`, `backend-error` und `missing-asset` (fehlende statische Datei im Build). - `bad-text` meldet sichtbare Texte wie „Invalid Date“, „NaN“, „undefined“, „[object Object]“ und das Wort „null“. Ab P2 ist die Prüfung im Gate scharf. - Alle Läufe nutzen die feste Uhr `FIXTURE_TODAY=2026-10-05` (überschreibbar per Umgebungsvariable). So sind Bilder verschiedener Tage vergleichbar. - Gate `p1` prüft `overflow-x` mit `['*', '!admin', '!admin-neu']`, weil die Admin-Seite heute schon waagerecht scrollt. P8 behebt das und nimmt die Ausnahme heraus. - Das Werkzeug ist ein eigenes Git-Repo unter `$W`. Wer es ändert, committet dort. Selbsttests: `node selftest.mjs` und `"$W/run-browser.sh" "$W/checks-selftest.mjs"`. - `scoped` schaltet eine weitere Prüfung nur für die genannten Ansichten ein. Beispiel: `icon-svg` für die Ansichten des Pakets, `ignore` mit Selektoren, die bis zu einem späteren Paket ausgenommen bleiben. - `step-failed`, `external-request` und `console-error` sind in jedem Gate aktiv. - Paket N legt `p` als `{ ...GATES['p'], scoped: { ...vorherige, 'icon-svg': { views: [… Ansichten von N und allen früheren Paketen] } } }` an. **Ansichten-IDs der Kundenseite** - Die Reiter-IDs kommen aus `src/lib/customerTabs.js`: `kunde-overview`, `kunde-tickets`, `kunde-invoices`, `kunde-projects`, `kunde-chat`, `kunde-abo`, `kunde-gutschriften`, `kunde-funktionen`, `kunde-protokoll`. - Pakete verwenden diese IDs und legen keine zweite Ansicht für dieselbe URL an. ## Review Focus Diese Eingaben und Zustände werden von keinem Lib-Test abgedeckt, treffen aber echte Nutzer. Jede Zeile hat eine Prüfung im Paket, das den Code besitzt. 1. **Altdaten in Tickets:** - Freitext-Datum `2.5.2026`, `31.02.2026` oder ISO-Datum - Ticket ohne `woid` - unbekannter oder leerer Status - fehlender `customerName` Erwartet wird, dass die Seite rendert und „–“ oder den Rohtext zeigt. Die Prüfung liegt in P0 (Testdaten) und P2 (Ansicht `ticket-altdaten`). 2. **Lange Inhalte am Handy:** - Betreff mit 120 Zeichen - Firmenname mit 60 Zeichen - viele Badges Erwartet wird kein waagerechtes Scrollen bei 390 px. Die Prüfung `overflow-x` liegt in P0 und ist ab P1 für alle Ansichten scharf. 3. **Teilausfall der Abfragen** (Zähler, Spalte „Zeit“, Mitarbeiterliste antworten mit 500): Die Liste bleibt bedienbar, statt Zahlen steht „–“. Die Prüfung ist das Szenario `fehler` in P0, angewendet in P2. 4. **Nur Tastatur:** - Dialog per Esc schließen - Tab bleibt im Dialog - „ד ist bei `busy` gesperrt - Tabellenzeilen per Tab erreichbar Die Prüfung liegt in P1 (Dialog) und P2 (Ticketzeilen), jeweils als Tastaturschritte in `views.mjs`. 5. **Viele Einträge:** - Ticket mit 300 Arbeitsblättern - Suche mit 250 Treffern Erwartet werden: Spalte „Zeit“ nur für die ersten 200 Zeilen mit Hinweis im Fuß, keine falschen Summen, Seite bleibt bedienbar. Die Prüfung ist das Testdaten-Set `viel` in P0, angewendet in P2.