diff --git a/docs/superpowers/plans/2026-10-05-weisses-design-00-uebersicht.md b/docs/superpowers/plans/2026-10-05-weisses-design-00-uebersicht.md new file mode 100644 index 0000000..99d1062 --- /dev/null +++ b/docs/superpowers/plans/2026-10-05-weisses-design-00-uebersicht.md @@ -0,0 +1,269 @@ +# 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` auf den Scratchpad-Ordner der Sitzung (sonst `$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..origin/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-*'`). +- `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. diff --git a/docs/superpowers/plans/2026-10-05-weisses-design-p0-vorbereitung.md b/docs/superpowers/plans/2026-10-05-weisses-design-p0-vorbereitung.md new file mode 100644 index 0000000..5af71e3 --- /dev/null +++ b/docs/superpowers/plans/2026-10-05-weisses-design-p0-vorbereitung.md @@ -0,0 +1,389 @@ +# 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..origin/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. diff --git a/docs/superpowers/plans/2026-10-05-weisses-design-p1-fundament.md b/docs/superpowers/plans/2026-10-05-weisses-design-p1-fundament.md new file mode 100644 index 0000000..e65a73e --- /dev/null +++ b/docs/superpowers/plans/2026-10-05-weisses-design-p1-fundament.md @@ -0,0 +1,749 @@ +# WOMS „Weißes Design“ P1: Fundament – 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 P1 ist das ganze Ticketsystem weiß und lesbar: +- Das Menü ist eine Textliste. +- Das Login ist neu. +- Alle Dialoge sind weiß. +- Die neuen Bausteine stehen für P2 bis P9 bereit. + +Die Innenlayouts der Seiten bleiben vorerst alt. + +**Architecture:** `src/styles/global.css` bekommt fünf Blöcke: + +1. Tokens und Aliase +2. Basis (Reset, Hilfsklassen, Links) +3. Bausteine (Prototyp-Klassen) +4. KOMPAT (Altklassen im neuen Look, bis P10) +5. Bereichs-CSS (Finanzen, Rechtliches), bis auf Pflicht-Fixes unverändert + +Gleichnamige Altdefinitionen werden vor dem Einfügen der Bausteine gelöscht. Die Komponenten unter `src/components/ui/` behalten ihre Schnittstellen. Neu kommen `Dialog`, `Stat`, `Logo` und `statusTone` hinzu. + +**Tech Stack:** React 18, react-router-dom 6, Vite 5, reines CSS. Tests mit `node --test`, Screenshots mit dem Werkzeug aus P0. + +**Spec:** `docs/superpowers/specs/2026-10-04-weisses-design-design.md`, Abschnitte „Gestaltungsregeln“, „Design-Tokens“, „Bausteine“, „Übergangsschicht KOMPAT“ und Paket P1. Die Prototyp-Werte stehen in `/root/reports/ticketsystem-design-2026-10-04/quelle/styles.css`. Globale Regeln, Befehlsregeln (`env.sh`, `check.sh`) und gemeinsame Schnittstellen stehen in `2026-10-05-weisses-design-00-uebersicht.md`. + +## Global Constraints + +Es gelten die Regeln der Übersicht. Für P1 kommt hinzu: + +- Jeder Run-Block beginnt mit `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh`. Danach gelten `$WT`, `$W` und `$TMPDIR`. +- Werte für Tokens, Abstände, Größen und Schatten werden aus der Spec bzw. aus `quelle/styles.css` übernommen und nicht neu erfunden. +- Nicht übernommen werden `html[data-accent]`, `.proto*` und `.nav-gap`. +- Verhalten, Routen und Auth bleiben unverändert. Ausnahmen: + - Der Footer entfällt. + - Das Einklappen entfällt. + - Die Schublade unter 860 px kommt neu dazu. +- Seiten in `src/pages/*` werden nur dort angefasst, wo dieser Plan es nennt: + - `LoginPage` + - `ProjectsPage`: Wurzelklasse + - `AdminPage`: Wurzelelement + - `ServerpulsPage`: Diagrammfarben +- Die Spec erlaubt keine neue `robots`-Meta-Zeile. `index.html` bekommt nur das Favicon. + +## Review Focus + +- **Übersichtspunkt 2 (lange Inhalte am Handy):** `overflow-x` ist im Gate `p1` für alle Ansichten scharf. +- **Übersichtspunkt 4 (Tastatur):** Die Esc- und Sperrlogik des Dialogs prüft ein Lib-Test in Task 7. +- **Altseiten:** Jede Altseite bleibt mit KOMPAT lesbar. Das prüft das Gate `p1` mit `dark-surface` und `low-contrast`, auch für Verläufe. +- **Alt-Dialoge:** Die zehn Alt-Dialoge müssen bei 390×700 und 1440×700 bis zum Speichern-Knopf erreichbar sein. Das prüft `submitReachable` in jeder `dlg-*`-Ansicht. + +--- + +### Task 0: Paketstart + +- [ ] **Step 1: Stand prüfen** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git fetch origin && git merge-base --is-ancestor design-p0 HEAD && git log --oneline origin/test..origin/feature/terminplaner-tp2 -- src/styles src/components/ui src/App.jsx src/components/Navbar.jsx src/pages/LoginPage.jsx src/lib/navLinks.js src/lib/ticketStatus.js && git rebase origin/test && "$W/check.sh"` +Expected: +- Exit 0. +- Die TP2-Zeile gibt nichts aus. Gibt sie etwas aus: stoppen und den Eigentümer fragen. Empfehlung: TP2 zuerst nach `test` bringen. + +- [ ] **Step 2: Gate `p1` um die Menü-Prüfung ergänzen** + +In `$W/gates.mjs` bekommt `p1` den Eintrag `scoped: { 'icon-svg': { views: '*', ignore: ['#inhalt *'] } }`. Damit prüft `icon-svg` ab P1 Menü, Mobil-Leiste und Login auf allen Ansichten, den Seiteninhalt aber erst ab dem jeweiligen Paket. + +### Task 1: `statusTone` + +**Files:** +- Modify: `src/lib/ticketStatus.js` (neuer Export nach `statusGroup`) +- Test: `tests/unit/ticketStatus.test.mjs` (neu) + +**Interfaces:** +- Consumes: `statusGroup(status)` aus derselben Datei. Es liefert die Gruppen-ID oder `null`. +- Produces: `statusTone(status) → '' | 'blue' | 'orange' | 'violet' | 'green' | 'plain'`. + +- [ ] **Step 1: Failing Test** + +```js +const { statusTone } = await loadModule('src/lib/ticketStatus.js') +test('statusTone: Ton je gespeichertem Wert', () => { + for (const s of ['Open', '', undefined, null, 'Completed']) assert.equal(statusTone(s), '') + for (const s of ['Occupied', 'Assigned', 'Added Info', 'In Kontakt']) assert.equal(statusTone(s), 'blue') + for (const s of ['Awaiting', 'Halted']) assert.equal(statusTone(s), 'orange') + assert.equal(statusTone('In Test'), 'violet') + for (const s of ['Closed', 'Zugesagt']) assert.equal(statusTone(s), 'green') + for (const s of ['Cancelled', 'Aborted', 'Abgesagt']) assert.equal(statusTone(s), 'plain') +}) +``` + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/ticketStatus.test.mjs` +Expected: FAIL, `statusTone is not a function`. + +- [ ] **Step 2: Implementieren** + +Zuordnung über `statusGroup`: + +| Gruppe | Ton | +|---|---| +| `open` | `''` | +| `working` | `'blue'` | +| `waiting` | `'orange'` | +| `review` | `'violet'` | +| `done` | `'green'` | +| `cancelled` | `'plain'` | +| `null` | `''` | + +`statusClass` bleibt bis P2. + +- [ ] **Step 3: Grün** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/ticketStatus.test.mjs && "$W/check.sh" --nur-tests` +Expected: PASS, Exit 0. + +- [ ] **Step 4: Commit** + +```bash +git add src/lib/ticketStatus.js tests/unit/ticketStatus.test.mjs +git commit -m "Ticketstatus: statusTone fuer die neuen Badge-Toene" -m "Co-Authored-By: Claude Opus 5.5 " +``` + +### Task 2: Tokens, Aliase, Basis, Schrift + +**Files:** +- Modify: `src/styles/global.css` + - Zeilen 1-10 (`@font-face`) löschen + - `:root`, alte Zeilen 18-71, ersetzen + - `body`/Scrollbar/`::selection`, alte Zeilen 73-106, ersetzen + - alte Hilfsregeln `.muted`/`.faint` (um Zeile 1051) ersetzen +- Modify: `src/pages/AdminPage.css` (Zeilen 2-3, `--admin-line*`, löschen) +- Delete: `src/assets/fonts/lato-regular.ttf`, `src/assets/fonts/OFL-Lato.txt` +- Modify (Werkzeug): `$W/views.mjs` bekommt die Ansicht `basis-weiss` + +**Interfaces:** +- Produces: alle Tokens und Aliase der Spec, dazu die Basis- und Hilfsklassen aus der Übersicht. + +- [ ] **Step 1: Prüf-Ansicht (failing)** + +`basis-weiss` öffnet `/tickets`. Der Schritt prüft, dass `getComputedStyle(document.body).backgroundColor === 'rgb(255, 255, 255)'` und dass `font-family` mit `-apple-system` beginnt. + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1-t2" --only basis-weiss --gate p0` +Expected: Exit 1 mit `step-failed`. + +- [ ] **Step 2: `:root` ersetzen** + +- Den Token-Block der Spec wörtlich übernehmen. +- Darunter folgt die Alias-Tabelle der Spec. Sie enthält `--admin-line` und `--admin-line-strong`. +- Die beiden Zeilen in `AdminPage.css` entfallen. + +- [ ] **Step 3: Basis setzen** + +Aus `quelle/styles.css` Zeilen 63-91 übernehmen. Zusätzlich gilt: + +| Regel | Wert | +|---|---| +| `html` | `color-scheme: light` | +| `body` | `font-family: var(--font); font-size: 14px; line-height: 20px; color: var(--text); background: var(--bg); -webkit-font-smoothing: antialiased`, ohne `overflow: hidden` | +| Formularelemente | `button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer }` und `input, select, textarea { font: inherit; color: inherit }` | +| `a` | `color: inherit; text-decoration: none` | +| `a:not([class])` | Unterstrich in `--line-strong`, `text-underline-offset: 3px`, beim Überfahren `text-decoration-color: currentColor` | +| Hilfsklassen | `.num` (`tabular-nums`), `.muted` (`--text-2`), `.faint` (`--text-3`), `.small` 13px, `.xs` 12/16, `.strong` 600, `.nowrap` | +| `.link`, `.link:hover`, `.link-danger`, `.link-danger:hover` | Werte aus dem Prototyp | +| `:focus-visible` | `outline: 2px solid #111111; outline-offset: 2px` | +| `:disabled` | `opacity: .55; cursor: not-allowed` | +| `[readonly]` | `background: var(--bg-subtle)` | + +- [ ] **Step 4: Schrift entfernen** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git rm -q src/assets/fonts/lato-regular.ttf src/assets/fonts/OFL-Lato.txt && rg -n "lato|Lato" src` +Expected: keine Treffer. + +- [ ] **Step 5: Grün** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1-t2" --only basis-weiss --gate p0` +Expected: Exit 0. + +- [ ] **Step 6: Commit** + +```bash +git add src/styles/global.css src/pages/AdminPage.css +git commit -m "Design: weisse Tokens, Aliase, Basis und Hilfsklassen, Systemschrift" -m "Co-Authored-By: Claude Opus 5.5 " +``` + +### Task 3: Bausteine-CSS + +**Files:** +- Modify: `src/styles/global.css`: Altdefinitionen gleichnamiger Klassen löschen, Block „Bausteine“ hinter der Basis anlegen + +**Interfaces:** +- Produces: alle Klassen aus der Übersicht („CSS-Klassen in `global.css`“). + +- [ ] **Step 1: Gleichnamige Altdefinitionen löschen** + +Gelöscht werden die Regeln dieser Selektoren samt ihrer Varianten (Hover, Kinder, Media-Queries). Die Zeilenangaben beziehen sich auf den Stand 603d08c, gesucht wird nach dem Selektor: + +| Selektor | Zeilen (603d08c) | +|---|---| +| `.btn` und `.btn:hover`, ohne `.btn-dark`/`.btn-teal`/`.btn-green` (die gehen nach KOMPAT bzw. sterben in Task 8) | ab 188 | +| `.table`, `.table th`, `.table td`, `.table td[rowspan]` | 236-275 | +| `.overlay` und `.overlay-close` (Neufassung in KOMPAT, Task 4) | ab 563 | +| `.page` | 867 | +| `.badge` (Basis) | 909-915 | +| `.urgent-toggle` | 971-976 | +| `.list-row` | 997-1005 | +| `.toolbar` | 1018 | +| `.seg` | 1019-1024 | +| `.empty` | 1028 | +| `.field` | 1032 | + +Der übrige alte Block „REDESIGN“ (796-1049) bleibt bis Task 4. Dazu gehören `.ui-card*`, `.ui-btn*`, `.badge-*`, `.kpi*`, `.tcard*`, `.ui-table`, `.list`, `.ticket-tabs`/`.ticket-tab*` und die Hilfsregeln `.mt-*`, `.flex*`, `.gap-*`, `.grow`, `.wrap` und `.text-ellipsis`. + +- [ ] **Step 2: Bausteine übernehmen** + +Aus `quelle/styles.css` werden diese Abschnitte übernommen: +- „Grundgerüst“ ohne `.nav-gap` +- „Seitenkopf“ +- „Knöpfe“ +- „Status-Marken“ +- „Reiter“ +- „Filterzeile“ +- „Tabellen“ +- „Kennzahlen / Schlüsselwerte“ +- „Ticket-Kopf“ +- „Listen“ +- „Dialog“ +- „Anmeldung“ + +Dazu kommen die Media-Queries 1100 und 860. **Ausgeschlossen** sind dort `.proto*`, `.ws*`, `.week`, `.day*` und `.appt` (sie kommen mit P2 und P3) sowie `html[data-accent]`. + +Zusätzlich: + +| Regel | Wert | +|---|---| +| `.page section + section` | `margin-top: 36px` | +| `.nav-dot` | `background: var(--nav-dot)` | +| `.nav-dot.critical` | `background: var(--danger)` | +| `.dialog-wide` | `max-width: 760px` | +| `.msg` | 13px | +| `.msg.err` | Farbe `--red-fg` | +| `.msg.ok` | Farbe `--green-fg` | +| `.msg.warn` | Farbe `--orange-fg` | +| `.loading` | `padding: 56px 0; text-align: center; color: var(--text-2)` | +| `.v.neg` | Farbe `--red-fg` | +| `.stat .sub` | `font-size: 12px; line-height: 16px; color: var(--text-2)` | +| `.badge.plain` | Farbe `--text-2` | +| `.tab.active`, `.tab[aria-selected="true"]` | aktiver Reiter | +| `.seg button.on`, `.seg button[aria-pressed="true"]` | aktiver Teil | +| `.autocomplete-list` | `background: var(--bg); box-shadow: var(--shadow-pop); border-radius: 8px` | +| `.autocomplete-item.active`, `.autocomplete-item:hover` | `background: var(--bg-hover)` | +| `.datetime-pair` | zwei Felder nebeneinander, Abstand 8px, kein `color-scheme: dark` | +| `[aria-invalid="true"]` und `.is-invalid` an `.input`, `.select`, `.textarea`, `.form-control` | `box-shadow: 0 0 0 1px var(--danger), 0 0 0 4px var(--danger-ring)` | + +- [ ] **Step 3: Jede Basisregel genau einmal** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && for s in btn table overlay page badge urgent-toggle list-row toolbar seg empty field; do printf '%s ' "$s"; rg -c "^\.$s( |\{|,)" src/styles/global.css; done` +Expected: Hinter jedem Namen steht `1`. + +- [ ] **Step 4: Bauen** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" --nur-build` +Expected: Exit 0. + +- [ ] **Step 5: Commit** + +```bash +git add src/styles/global.css +git commit -m "Design: Bausteine aus dem Prototyp, gleichnamige Altregeln ersetzt" -m "Co-Authored-By: Claude Opus 5.5 " +``` + +### Task 4: KOMPAT und tote Klassen + +**Files:** +- Create: `$W/css-klassen.mjs` +- Create: `docs/superpowers/plans/2026-10-05-weisses-design-p1-tote-klassen.md` +- Modify: `src/styles/global.css` (Block „KOMPAT“, Legacy löschen) + +**Interfaces:** +- Produces: + - Der KOMPAT-Block nach der Spec-Tabelle „Übergangsschicht KOMPAT“. P10 entfernt ihn. + - `css-klassen.mjs [--ohne-ausnahmen]` gibt JSON aus: `{ defined, used, dead, undefinedUsed }`. + +- [ ] **Step 1: Skript schreiben** + +„Benutzt“ ist eine Klasse, wenn eine dieser Bedingungen zutrifft: +- Sie steht als Wort in einem `className`-Literal oder Template-String in `src/**/*.jsx`. +- Sie steht in einer Zeichenkette in `src/lib/*.js` oder `src/hooks/*.js`. +- Ein Template-Präfix deckt sie ab: `xxx-${…}` gilt für `xxx-*`. +- Sie steht in der Ausnahmeliste: `fin-neg`, `fin-fixed-off`, `legal-tone-*`, `legal-version-*`, `badge-*`, `status-*`, `ticket-tab-active`, `ui-btn-*`, `side-dot-*`, `woms-chat`, `chat-*`. Mit `--ohne-ausnahmen` entfällt diese Liste. + +„Definiert“ ist eine Klasse, wenn sie in einem Selektor in `global.css`, `chat.css`, `serverpuls.css`, `admin-areas.css` oder `pages/AdminPage.css` steht. + +- [ ] **Step 2: Liste erzeugen und gegenprüfen** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node "$W/css-klassen.mjs" "$WT" > "$TMPDIR/klassen.json"` + +Danach für jeden Kandidaten in `dead`: `rg -w src --glob '!*.css'`. Ein Kandidat gilt nur bei 0 Treffern als tot. Die bestätigte Liste kommt mit Klassenname und definierender Datei:Zeile in `2026-10-05-weisses-design-p1-tote-klassen.md`. + +Expected: etwa 66 Namen. Liegt die Zahl unter 40 oder über 90, wird die Abweichung dort begründet. + +- [ ] **Step 3: Legacy löschen, KOMPAT anlegen** + +Gelöscht werden: +- die Regeln der bestätigten toten Klassen +- alle verbliebenen `!important` +- `.side*` außer `.side-count` und `.side-logo` (die gehen nach KOMPAT) +- `.footer`, `.navbar*`, `.ticket-row*`, `.worksheet-expansion*`, `.slider`, `.drop-zone`, `.modern-card` +- die Legacy-Responsive-Regeln (alt 789-794) +- die Keyframes `slideDown`, `fadeIn` und `slideIn` +- `.kpi*`, weil `Kpi` ab Task 6 `Stat` rendert + +Danach wird KOMPAT laut Spec-Tabelle angelegt, markiert mit `/* ===== KOMPAT (bis P10) ===== */`. Der Block enthält auch `.page .page` und `.page .main-content`, die Abstand und Breite neutralisieren. + +- [ ] **Step 4: Keine benutzte Klasse ohne Regel** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node "$W/css-klassen.mjs" "$WT" | node -e "const j=JSON.parse(require('fs').readFileSync(0));console.log(j.undefinedUsed.join('\n'))"` +Expected: Nur diese Namen tauchen auf: +- `p-4`, `btn-sm`, `status-filter`, `supporters-editor`, `timeline` +- die Bootstrap-Namen aus `WorksheetList.jsx` (heute schon wirkungslos) + +- [ ] **Step 5: Altseiten ansehen** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1-t4" --gate p1; echo "exit $?"` +Expected: `step-failed` (`submitReachable`) ist auf keiner `dlg-*`-Ansicht mehr gemeldet. Erlaubt sind nur noch Treffer `dark-surface` und `low-contrast` in Dateien aus Task 9. + +- [ ] **Step 6: Commit** + +```bash +git add src/styles/global.css docs/superpowers/plans/2026-10-05-weisses-design-p1-tote-klassen.md +git commit -m "Design: KOMPAT-Schicht fuer Altklassen, tote Regeln und !important entfernt" -m "Co-Authored-By: Claude Opus 5.5 " +``` + +### Task 5: Gerüst, Menü, Logo, Favicon + +**Files:** +- Create: `src/components/Logo.jsx`, `public/favicon.svg` (Kopie von `quelle/favicon.svg`) +- Modify: + - `src/App.jsx` + - `src/components/Navbar.jsx` + - `src/lib/navLinks.js` (`hot: true` am Chat-Unterpunkt) + - `index.html` (nur ``) + - `src/styles/serverpuls.css` (Zeilen 70-74 `.side-dot*` löschen) + - `src/pages/ProjectsPage.jsx` (Wurzel `main-content` → `page`) + - `src/pages/AdminPage.jsx` (`
` → `
`, zwei Stellen) +- Delete: `src/components/Footer.jsx`, `src/lib/sidebar.js`, `tests/unit/sidebar.test.mjs` +- Test: `tests/unit/navLinks.test.mjs` +- Modify (Werkzeug): `$W/views.mjs` bekommt die Ansicht `menue-mobil` + +**Interfaces:** +- Consumes: Klassen aus Task 3. +- Produces: + - `Logo({ size = 24 })` mit `aria-hidden="true"`. Die Pfade kommen aus `quelle/app.js`, Konstante `LOGO`. + - `Sidebar({ onNavigate })`. + +- [ ] **Step 1: Test für `hot` (failing)** + +In `tests/unit/navLinks.test.mjs` die Zeile `assert.equal(kunden.children.find((c) => c.key === 'chat').hot, true)` ergänzen. Die bestehenden `deepEqual` bleiben. + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/navLinks.test.mjs` +Expected: FAIL. + +- [ ] **Step 2: `navLinks.js` ergänzen, Test grün** + +Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/navLinks.test.mjs` +Expected: PASS. + +- [ ] **Step 3: Logo und Favicon anlegen** + +- [ ] **Step 4: Gerüst in `App.jsx`** + +`AppContent` hält `menuOpen` und baut dieses Gerüst: + +```jsx +
+ +
+
+
+ WEBklar + +
+
+
+
+``` + +- Ein Effekt auf `location.pathname` (nur darauf) schließt die Schublade und ruft `window.scrollTo(0, 0)` auf. +- Ein Esc-Listener ist nur bei `menuOpen` aktiv. +- Der Ladezustand von `ProtectedRoute` ist `
Wird geladen …
`. + +- [ ] **Step 5: `Navbar.jsx` als Textliste** + +Aufbau: `