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

19 KiB
Raw Permalink Blame History

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.