Files
tickte-system/docs/superpowers/plans/2026-10-05-weisses-design-00-uebersicht.md
T

275 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<N>` 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<N>` 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 <skript> [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 -- <Dateien des Pakets>` 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<N-1> 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<N-1>..design-p<N>` als Commit auf `test`. Das gilt nur für das jüngste Paket.
**Bausteine benutzen**
- Jeder Knopf in einem `<Dialog as="form">` 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 <noreply@anthropic.com>`. 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 <noreply@anthropic.com>`.
- Niemals `git push`. Live bringt nur der Eigentümer mit `git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p<N>^{commit}:refs/heads/test'`.
- Vor dem Paketstart und vor dem Setzen des Tags: `git fetch origin && git rebase origin/test`. Das Tag `design-p<N>` wird danach gesetzt und nie bewegt. Korrekturen nach dem Push: `design-p<N>-fix<k>`.
- 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/<name>.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 `<section className={className}>`, mit Titel `<h2 className="section"><span>{title}</span>{actions && <span className="actions">{actions}</span>}</h2>`.
- Ohne `title` und ohne `actions` entfällt das `h2`, die Kinder stehen direkt in `<section>`.
- `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 `<span class="count">` 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 `<Stat … tone={accent === 'var(--danger)' ? 'neg' : ''}>` zurück und ignoriert `icon`.
- Wird in P7 gelöscht.
- **`src/components/ui/StatusPill.jsx`**
- `StatusPill({ status })` → `<span className={'badge ' + statusTone(status)}>{statusLabel(status)}</span>`.
- `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 `<h3 id>` und `<button type="button" className="dialog-close" aria-label="Schließen" disabled={busy}>×</button>`, darunter `.dialog-body` und `.dialog-foot`.
- Mit `as="form"` ist die Box ein `<form onSubmit>`, 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 <name>` | Vite-Build mit Test-Endpunkten nach `$W/dist/<name>` |
| `shots.sh <name> <ausgabe> [--only id,id] [--gate p<N>]` | Screenshots plus Prüfbericht `<ausgabe>/report.json` |
| `run-browser.sh <skript.mjs> [args]` | jedes andere Browser-Skript unter Speichergrenze |
Zu `shots.sh`:
- Eine unbekannte ID in `--only` bricht mit Exit 2 ab.
- `--gate p<N>` 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<N>: { 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<N>` als `{ ...GATES['p<N-1>'], 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.