19 KiB
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.cssan. 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 --testmit 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-p1enthä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-designW=/root/reports/ticketsystem-design-2026-10-04/werkzeugTMPDIR=$W/tmpset -o pipefailcd "$WT"
Temporäre Dateien liegen nie direkt in
/tmp. Auch die ```bash-Commit-Blöcke der Pläne laufen in einem Shell-Aufruf, der zuerstenv.shlä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/# failund die Bundle-Größen aus und endet mit dem Exit-Code vonnpm testbzw.npm run build. Ein Tag wird nur mit"$W/check.sh" && git tag design-p<N>gesetzt. Ketten wienpm test | grep … && …sind verboten, weil sie den Exit-Code vongrepwerten. -
Browser-Skripte (Playwright, Pixelvergleich) laufen nie direkt mit
node, sondern über die Hüllen$W/shots.shoder$W/run-browser.sh <skript> [args]. Beide starten untersystemd-run --scope -q -p MemoryMax=1500M -p MemorySwapMax=0. -
rgauf diesem Host ist Version 13.0.0 ohne PCRE2. Deshalb keinrg -P, keine Lookarounds.
CSS-Reihenfolge
src/main.jsximportiertAppvor./styles/global.css. Deshalb stehen alle von Komponenten importierten Stylesheets im Bundle vorglobal.css:chat.css,serverpuls.css,admin-areas.css,pages/AdminPage.cssunduplot.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-tp2arbeitet eine andere Sitzung weiter. Stand 2026-10-05: 5 Commits vortest, sie berühren u. a.CustomerFunktionen.jsx,lib/funktionenView.jsund 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 nachtestzu bringen.
Paketstart und Abnahme-Reihenfolge
- Paketstart:
git fetch origingit merge-base --is-ancestor design-p<N-1> HEAD, sonst stoppen und meldengit 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 auftest. Das gilt nur für das jüngste Paket.
Bausteine benutzen
- Jeder Knopf in einem
<Dialog as="form">außer dem Absenden-Knopf trägttype="button".Buttonsetzt 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 keinesectionsind. - Klickbare Tabellenzeilen prüfen Klicks immer mit
isRowActivation(event)aussrc/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-designauf dem Branchfeature/design-weiss. In/home/webklar/apps/ticket.webklar.com/build/(Live-Checkout) wird nie etwas geändert. git addnur mit ausdrücklichen Pfaden, nie-Aoder..- 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 mitgit -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 Tagdesign-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.jssamt 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 untersystemd-run --scope -q -p MemoryMax=1500M -p MemorySwapMax=0, ein Build zusätzlich mitNODE_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.mjsim 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.jsbleiben 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. sizewird ignoriert.
- Signatur:
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, dazuvioletundplain. - Die Prop
dotwird ignoriert.
- Signatur:
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
titleund ohneactionsentfällt dash2, die Kinder stehen direkt in<section>. pad,bodyStyleundstylewerden ignoriert.
- Signatur:
src/components/ui/PageHeader.jsx- Signatur:
PageHeader({ title, subtitle, actions, eyebrow, crumb, children }). crumbist ein Array[{ label: string, to?: string }], gerendert als.crumbmit „/“ in--text-3vor.page-head.
- Signatur:
src/components/ui/Tabs.jsx- Signatur:
Tabs({ tabs: [{ id, label, count? }], active, onChange }). - Rendert
.tabs,role="tablist", Buttons.tabmitrole="tab",aria-selected, Zähler<span class="count">beicount != null.
- Signatur:
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 = '' }), wobeitone'' | 'warn' | 'neg'ist. - Rendert
.statmit.k(Label),.v {tone}(Wert) und.sub. - Der Container
.statskommt vom Aufrufer.
- Signatur:
src/components/ui/Kpi.jsx- Signatur:
Kpi({ label, value, sub, icon, accent }). - Gibt
<Stat … tone={accent === 'var(--danger)' ? 'neg' : ''}>zurück und ignorierticon. - Wird in P7 gelöscht.
- Signatur:
src/components/ui/StatusPill.jsxStatusPill({ status })→<span className={'badge ' + statusTone(status)}>{statusLabel(status)}</span>.UrgentPill({ priority })→badge red„Dringend“, nur wennisUrgent(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-headmit<h3 id>und<button type="button" className="dialog-close" aria-label="Schließen" disabled={busy}>×</button>, darunter.dialog-bodyund.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.
- Signatur:
src/components/Logo.jsx:Logo({ size = 24 })mit dem SVG aus dem Prototyp (app.js, KonstanteLOGO).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
--onlybricht 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.backendgibt Zugriff aufcalls(alle beantworteten Requests) undviolations.- Ein Fehler in
stepserzeugt 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[] } } } }. checksgilt fürviews. Einträge inviewsdü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-errorundmissing-asset(fehlende statische Datei im Build). bad-textmeldet 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
p1prüftoverflow-xmit['*', '!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.mjsund"$W/run-browser.sh" "$W/checks-selftest.mjs". scopedschaltet eine weitere Prüfung nur für die genannten Ansichten ein. Beispiel:icon-svgfür die Ansichten des Pakets,ignoremit Selektoren, die bis zu einem späteren Paket ausgenommen bleiben.step-failed,external-requestundconsole-errorsind 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.
-
Altdaten in Tickets:
- Freitext-Datum
2.5.2026,31.02.2026oder 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). - Freitext-Datum
-
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-xliegt in P0 und ist ab P1 für alle Ansichten scharf. -
Teilausfall der Abfragen (Zähler, Spalte „Zeit“, Mitarbeiterliste antworten mit 500): Die Liste bleibt bedienbar, statt Zahlen steht „–“. Die Prüfung ist das Szenario
fehlerin P0, angewendet in P2. -
Nur Tastatur:
- Dialog per Esc schließen
- Tab bleibt im Dialog
- „ד ist bei
busygesperrt - Tabellenzeilen per Tab erreichbar
Die Prüfung liegt in P1 (Dialog) und P2 (Ticketzeilen), jeweils als Tastaturschritte in
views.mjs. -
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
vielin P0, angewendet in P2.