57 KiB
WOMS „Weißes Design“ P6: Kunden-Unterseiten – 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: Chat, Gutschriften, Abo-Angebote und E-Mail-Speicher sind hell und kastenlos. Die Chat-Liste besteht aus Haarlinien-Zeilen, die drei Listen sind Tabellen mit Spaltenpriorität, das Angebots-Formular ist ein Dialog. Der Chat wechselt bei 860 px zwischen Liste und Verlauf. admin-areas.css ist am Ende gelöscht, weil P4 die Kundenseite davon gelöst hat und P6 die letzten Nutzer umbaut.
Architecture:
chat.csswird neu geschrieben. Jede Regel für Elemente im Chat-Container trägt das Präfix.woms-chatund ist strikt spezifischer als die Fremdregel des Portal-Bausteins. Das Seitenlayout (.chat-pageusw.) bleibt ohne Präfix.- Die Listen nutzen
Tabs,.tablemit.hide-md/.hide-sm,Badge,EmptyState.CreditDetailwird.two-colmitCard-Abschnitten und.kv.OfferFormwird einDialog. - Neue reine Helfer liegen in
src/libund sind per Test gesichert. Das Screenshot-Werkzeug bekommt den echten Portal-Chat-Baustein, damit die Chat-Regeln gegen das Fremd-CSS geprüft werden.
Tech Stack: React 18, react-router-dom 6, reines CSS, node --test, Prüf- und Screenshot-Werkzeug aus P0 (env.sh, check.sh, shots.sh).
Spec: docs/superpowers/specs/2026-10-04-weisses-design-design.md, Abschnitte „Gestaltungsregeln“, „Bausteine“, „Übergangsschicht KOMPAT“, Paket P6, „Seiten ohne Prototyp-Vorlage“, „Texte“, „Was gleich bleibt“, Risiken (Chat-Composer, Vorschlagslisten). Anhang …-umbaukarte.md: Abschnitt 5 (P6), 7.2, 8.1 Punkte 13, 14, 19. Globale Regeln: 2026-10-05-weisses-design-00-uebersicht.md.
Global Constraints
Es gelten die Regeln der Übersicht, besonders „Befehle und Prüfungen“, „CSS-Reihenfolge“, „Parallele Arbeit an WOMS“, „Paketstart und Abnahme-Reihenfolge“ und „Bausteine benutzen“. Jeder Run-Block beginnt mit source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh &&. Danach gelten $WT (Worktree, Arbeitsverzeichnis), $W (Werkzeug) und $TMPDIR. Für P6 kommt hinzu:
- Voraussetzung: Tag
design-p5ist gesetzt. P4 hatCustomerFunktionen,AboStatusCardundKundenansichtButtonumgebaut, alle.funktionen-*-Regeln nachglobal.cssverlegt und die beiden Dateien vonadmin-areas.cssgelöst (keine.area-*-Klasse, kein Import). Task 1 prüft das. - Dateien: nur
CustomerChatPage,CreditsPage,OffersPage,StorageTasksPage,src/components/{chat,credits,offers}/*,src/styles/{chat,admin-areas,global}.css,src/lib/{credit,offer,storage}Format.jssamt Tests und$W. Nie anfassen:loadChatComposer.jssamt Test, die Container inChatThread.jsx, das Portal-Repo (nur Lesezugriff für den Snapshot in Task 2), die P4-Dateien. - Ladereihenfolge:
main.jsximportiertAppvorglobal.css, also stehtchat.cssim Bundle vorglobal.css, und das Portal-CSS lädt zur Laufzeit zuletzt. Gleiche Spezifität verliert immer:.btnausglobal.cssüberstimmt ein.chat-back { display: none }. Seitenregeln für Elemente mit globaler Klasse brauchen höhere Spezifität (.chat-page .chat-back), nie!important. - Texte: Erlaubt sind nur „← Alle Unterhaltungen“ (Pfeil statt Icon), „Speichert …“ und „Wird geladen …“ (statt Spinner). Alles andere bleibt wörtlich, auch Spaltenköpfe, Hinweisabsätze und alle
confirm/alert-Texte. - Hygiene und Inventur: In den zehn P6-Dateien gibt es danach kein
react-icons, keinsize="sm", keinstyle={{(heute 26). Treffer je Datei fürwindow.confirm,window.alert,busyRef,data-storage-done,id="calc-*",id="credit-*"bleiben:CreditDetail.jsx16 (1 confirm, 9 busyRef, 6 IDs),OfferList.jsx4,OfferForm.jsx1,StorageTasksPage.jsx3.
Entscheidungen (die Spec ist dazu still):
- Entscheidung: Die Prüf-Zeichen
✓,!,ider „Automatischen Prüfung“ bleiben als Badge-Text. Die Umbaukarte sagt „ohne Glyphen“, aber die Spec „Texte“ erlaubt keine Textänderung dafür, und sonst trüge nur die Farbe den Status. - Entscheidung: Die Filter sind
Tabsohne Zähler (die Spec nennt keine, es gibt keine neuen Abfragen). - Entscheidung: Tabellenzeilen sind nicht klickbar (heute auch nicht), kein
tr.rowund damit keinisRowActivation(src/lib/rowClick.jsaus P2). Aktionen stehen alsbutton.linkbzw..link-dangerin der letzten Zelle. Die offene Gutschrift trägtaria-current="true". - Entscheidung: Spaltenpriorität (immer /
hide-sm/hide-md):- Gutschriften: Kunde, Status, Aktion / Eingang, Rechnung / Leistung, Bezahlt bis.
- Angebote: Kunde, Angebot, Status mit Aktionen / Gültig bis / Angelegt.
- Speicher: Kunde, Aufgabe, Aktion / Fällig / Bestellt.
- Entscheidung: Der Chat-Zähler ist eine kleine Zahl in
--accent, fett (wie.nav-count.hot), keine Pille. Die aktive Zeile hataria-current="true"und--accent-soft. - Entscheidung: Beim Portal-Composer bleibt die Geometrie (46 px, runde Knöpfe). Überschrieben werden Farben, Ring statt Rand, Hover, Fokus, Deaktiviert und unter 640 px wieder
font-size: 16pxim Eingabefeld (das heutigefont: inherithebt die Portal-Regel gegen das iOS-Zoomen auf). Der Datei-Chip ist weiß mit Ring, weil die Blase selbst--bg-hoverist. Die Anhang-Menü-Icons bleiben. - Entscheidung: Lightbox und Kamera bleiben unberührt dunkel (am
body, vom Präfix nicht erreichbar). Der Kamera-Knopf „Foto verwenden“ bleibt im Portal-Lila und geht als „bewusst offen“ in die Übergabe. - Entscheidung:
OfferFormbleibt ohne<form>(also keinDialog as="form", kein Enter-Submit, wie heute). Beide Fußknöpfe tragen trotzdemtype="button". „Abbrechen“ ist beim Speichern gesperrt, passend zu „ד und Esc. - Entscheidung:
admin-areas.csswird in P6 gelöscht. P4 hat die.funktionen-*-Regeln nachglobal.cssverlegt undCustomerFunktionenundAboStatusCardvon der Datei gelöst. Die P6-Seiten verlieren ihre.area-*-,.credit-*-,.offer-*- und.storage-row-Klassen und den Import. Danach hat die Datei keinen Nutzer mehr (Task 11). - Entscheidung: Kleine Ergänzungen stehen in einem Block „Kunden-Unterseiten (P6)“ am Ende von
global.css.table-wrapgilt nur für die drei P6-Tabellen, damit P2 bis P5 unberührt bleiben.
Review Focus
Aus der Übersicht gehören zu P6:
- Punkt 2, lange Inhalte am Handy: Kundenname mit 60 Zeichen in allen Listen, E-Mail mit 50 Zeichen ohne Leerzeichen, URL mit 80 Zeichen im Chat, Angebotstext und interne Notiz je 200 Zeichen.
overflow-xbei 390 px in allen P6-Ansichten. - Punkt 3, Teilausfall: Das Szenario
fehler-portallässt alle/api/team/*-Anfragen mit 500 antworten. Die vier Seiten zeigen.msg.errund bleiben bedienbar, „Erneut versuchen“ lädt neu. - Punkt 4, nur Tastatur: Tabellenaktion per Tab erreichbar. Esc schließt erst die Vorschlagsliste, dann den Dialog. Tab bleibt im Dialog, „ד und Esc sind beim Speichern gesperrt. Doppelklick auf „Genehmigen & gutschreiben“ löst einen POST aus, der 300-ms-Debounce einen Berechnungs-POST.
- Punkt 5, viele Einträge:
chat-viel(150 Unterhaltungen, die Liste scrollt in sich) undgutschriften-viel(120 Anträge).
Dazu gilt für P6:
- Fremd-CSS gewinnt nicht:
chat-verlaufliest berechnete Stile und vergleicht sie mit Sollwerten (Task 4)..chat-backist bei 1440 px unsichtbar, bei 390 px sichtbar. - Vorschlagsliste im Dialog ist vollständig erreichbar, der längste Speichern-Text passt bei 390 px in die Fußzeile.
- Zwischenstufe 1000 px: Die Ansichten
*-tabletzeigen die Spaltenpriorität.
Task 1: Ausgangslage und Lib-Helfer
Files:
- Modify:
src/lib/creditFormat.js,src/lib/offerFormat.js,src/lib/storageFormat.js(neue Exporte, Stand vor P6) - Test (neu):
tests/unit/creditFormat.test.mjs,tests/unit/offerFormat.test.mjs,tests/unit/storageFormat.test.mjs
Interfaces:
-
Consumes:
fmtDate,fmtDateTime,formatCentsaussrc/lib/format.js,isOverdue. -
Produces:
creditActionLabel(status) → 'Prüfen' | 'Ansehen'creditStatusNote(r) → stringcheckGlyph(status) → '✓' | '!' | 'i'checkTone(status) → 'ok' | 'warn' | 'info' | 'muted'filterOffers(offers, filter) → offers[]offerRowActions(o) → { sendLabel: '' | 'E-Mail senden' | 'Erneut senden', canWithdraw: boolean }offerMailLine(o) → null | { tone: 'muted' | 'warn', text }storageDue(t, now) → { tone, label, sub, subMuted }
-
Step 1: Paketstart und Ausgangslage prüfen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git status --short && git fetch origin && git merge-base --is-ancestor design-p5 HEAD && git rebase origin/test && "$W/check.sh"
Expected: Exit 0, git status --short zeigt keine geänderten Dateien, # fail 0. Die Testzahl wird als N5 notiert. Schlägt merge-base fehl, fehlt design-p5 im Branch: stoppen und melden.
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git log --oneline origin/test..feature/terminplaner-tp2 -- src/pages/CustomerChatPage.jsx src/pages/CreditsPage.jsx src/pages/OffersPage.jsx src/pages/StorageTasksPage.jsx src/components/chat src/components/credits src/components/offers src/lib/creditFormat.js src/lib/offerFormat.js src/lib/storageFormat.js src/styles/chat.css src/styles/admin-areas.css src/styles/global.css
Expected: keine Ausgabe. Gibt es Treffer, stoppt der Agent und fragt den Eigentümer. Er empfiehlt, TP2 zuerst nach test zu bringen.
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|ui-btn|FaSpinner" src/components/customer/KundenansichtButton.jsx src/components/customer/AboStatusCard.jsx src/components/customer/CustomerFunktionen.jsx; rg -n "admin-areas|\barea-" src/components/customer; rg -n "funktionen-" src/styles/admin-areas.css
Expected: keine Treffer. Ein Treffer heißt, dass P4 CustomerFunktionen oder AboStatusCard nicht vollständig von admin-areas.css gelöst hat: stoppen und melden, die P4-Dateien werden in P6 nicht umgebaut.
- Step 2: Failing Tests schreiben (Leerzeichen in Beträgen ist U+00A0)
creditFormat.test.mjs (4 Tests):
creditStatuspinnt die Tonnamen:offen→{ tone: 'warn', label: 'Zu prüfen' },rueckfrage→muted/„Rückfrage beim Kunden“,genehmigt→ok/„Genehmigt“,abgelehnt→danger/„Abgelehnt“,'neu'→muted/'neu'.creditActionLabel:offen,rueckfrage→'Prüfen', sonst'Ansehen'.creditStatusNote:{ status: 'genehmigt', amountCents: 1250, appliedAt: '2026-10-01T10:00:00Z' }→'12,50 €', ohneappliedAt→'12,50 € · nicht gebucht',{ status: 'offen', amountCents: 1250 }→''.checkGlyph/checkTone:ok→'✓'/'ok',warn→'!'/'warn',info→'i'/'info',undefined→'i'/'muted'.
offerFormat.test.mjs (4 Tests):
offerStatus:offen→warn/„offen“,aktiv→ok/„aktiv“,abgelaufen→muted/„abgelaufen“,zurueckgezogen→muted/„zurückgezogen“.filterOffersmit Statusoffen, aktiv, abgelaufen, zurueckgezogen(ids1bis4):'open'→['1','2'],'all'→ alle,null→[], Eingabe unverändert.offerRowActions:{ status: 'offen', mailSendable: true }→{ sendLabel: 'E-Mail senden', canWithdraw: true }, mitnotifiedAt→'Erneut senden',{ status: 'aktiv', mailSendable: true }→canWithdraw: false,{ status: 'abgelaufen', mailSendable: false }→{ sendLabel: '', canWithdraw: true },{ status: 'zurueckgezogen' }→{ sendLabel: '', canWithdraw: false }.offerMailLine:{ notifiedAt: '2026-09-30T10:05:00.000Z' }→{ tone: 'muted', text: 'E-Mail gesendet 30.09.26, 12:05' },{ mailSendable: true }→{ tone: 'warn', text: 'E-Mail nicht verschickt' },{ status: 'offen', customerEmail: '' }→{ tone: 'warn', text: 'Keine E-Mail-Adresse hinterlegt' },{ status: 'abgelaufen', customerEmail: '' }und{ status: 'offen', customerEmail: 'a@b.de' }→null.
storageFormat.test.mjs (2 Tests, t0 = Date.parse('2026-10-05T08:00:00Z')):
-
storageDue:{ kind: 'einrichten', createdAt: '2026-10-01T08:30:00.000Z' }→{ tone: 'warn', label: 'jetzt einrichten', sub: 'bestellt 01.10.26, 10:30', subMuted: true }.{ kind: 'senken', dueAt: '2026-10-04T00:00:00.000Z' }→{ tone: 'warn', label: 'zum Periodenende', sub: '04.10.2026', subMuted: false }, mitdueAt: '2026-10-20T00:00:00.000Z'→tone: 'muted',sub: '20.10.2026'. -
isOverdue: vergangenesdueAt→true, zukünftiges oder fehlendes →false. -
Step 3: Test laufen lassen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/creditFormat.test.mjs tests/unit/offerFormat.test.mjs tests/unit/storageFormat.test.mjs
Expected: FAIL (… is not a function).
- Step 4: Implementieren
Die Regeln sind die heutigen Inline-Ausdrücke, herausgezogen: creditActionLabel aus CreditList.jsx Z. 52, creditStatusNote Z. 48, checkGlyph und checkTone aus CreditDetail.jsx Z. 11 und 211, filterOffers aus OfferList.jsx Z. 54, offerRowActions und offerMailLine Z. 93 bis 109 (Reihenfolge der Mail-Zeile: notifiedAt, mailSendable, offen ohne Adresse, sonst null), storageDue aus StorageTasksPage.jsx Z. 57 bis 59. Zeichenketten bleiben wörtlich.
- Step 5: Tests grün und Commit
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" --nur-tests
Expected: Exit 0, # pass = N5 + 10, # fail 0.
git add src/lib/creditFormat.js src/lib/offerFormat.js src/lib/storageFormat.js tests/unit/creditFormat.test.mjs tests/unit/offerFormat.test.mjs tests/unit/storageFormat.test.mjs
git commit -m "Design P6: reine Helfer fuer Gutschrift-, Angebots- und Speicherzeilen mit Tests" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 2: Werkzeug: echter Chat-Baustein, Fixtures, Ansichten, Gate p6
Files (unter $W/, außerhalb des Repos, kein Commit): Create fixtures/portal/{chat-composer.css,chat-composer.js,README.md}. Modify backend.mjs, fixtures/standard.mjs, checks.mjs, views.mjs, gates.mjs.
Interfaces:
-
Consumes:
createBackend,VIEWS,GATES,runChecksaus P0, Gatep5.ctx.backend.calls(Liste{ method, url, body }aller beantworteten Anfragen). -
Produces: Szenario
fehler-portal, die Ansichten unten, Gatep6. -
Step 1: Portal-Baustein als Snapshot
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && cp /home/webklar/apps/kundenbereich.webklar.com/build/public/chat-composer.{css,js} "$W/fixtures/portal/" && sha256sum "$W"/fixtures/portal/chat-composer.*
Expected: css b34fbaeb72922ddfbd57f1074e58e58dffc560cd1e9a31c98b12e66487885647, js ebb79581f9b307b303877f169fa5546bb98b4fc66320c40ae8fd78c965bace23. Die README nennt Quelle, Datum 2026-10-05 und beide Hashes.
-
Step 2: Backend erweitern
-
https://portal.test/chat-composer.cssund.jsliefern den Snapshot (statt der leeren Dateien aus P0). -
Anhang-Adressen unter
https://portal.test/api/team/files/*liefern ein 1×1-PNG bzw. 200 ohne Inhalt. -
POST …/chats/:id/attachmentsantwortet im Szenariofehler-portalund inchat-anhang-fehlermit 500. -
POST …/offersundPOST …/takeover-credits/:id/decisionantworten nach 800 ms. -
Szenario
fehler-portal: jede Anfrage anhttps://portal.test/api/team/*bekommt 500 im Fehlerformat des Szenariosfehler. Deren Konsolenmeldungen gelten als absichtlich. -
Step 3: Fixtures ergänzen (Schlüssel nach dem Muster der vorhandenen, Formen laut Lesecode in den
*Format.jsund JSX-Dateien)
| Bereich | Fälle |
|---|---|
| Chats | 8 Unterhaltungen, 3 mit unreadFromCustomer (1, 12, 120), ein 60-Zeichen-Name, ein lastText mit 80-Zeichen-URL. Satz viel mit 150. |
| Verlauf Kunde A | 7 Nachrichten: Kundentext, Teamtext (senderName „Kenso Grimm“), Bild (kind: 'image', 640×480), PDF (Angebot-Kuehn-2026.pdf, 183000 Byte), Sprachnachricht (kind: 'voice', durationMs: 23000), Teamtext mit 80-Zeichen-URL, langer Kundentext. |
| Gutschriften | 5 Anträge (offen, rueckfrage, genehmigt gebucht, genehmigt ungebucht, abgelehnt), einer mit 60-Zeichen-Name und langem services/provider/domain. Satz viel mit 120. getCredit für einen offenen und einen ungebuchten genehmigten Antrag: 3 requiredChecks, autoChecks mit ok/warn/info (mit detail), calc, laufendes Abo, 1 weiterer Antrag, 2 Dateien, 3 Verlaufseinträge. POST …/calc → { calc }. |
| Angebote | 6 Angebote: offen mit mailSendable ohne notifiedAt, offen ohne E-Mail, aktiv mit scope: 'laufend', abgelaufen mit mailSendable und notifiedAt, zurueckgezogen, eins mit 60-Zeichen-Name, 50-Zeichen-E-Mail und 200-Zeichen-internalNote. getOfferContext für einen Kunden ohne Abo und einen mit laufendem Wartungs-Abo (upcomingCharges). |
| Speicher | tasks: einrichten, senken überfällig, senken später, einer partial mit error, einer mit 60-Zeichen-Name. failed mit 2 Einträgen, allocatedGb über warnGb. |
-
Step 4: Prüf-Ausnahmen erweitern
-
In
checks.mjs:dark-surfacenimmt zusätzlich.woms-chat :is(.chat-send, .chat-audio-play, .chat-file-ext, .chat-tray-remove, .chat-tray-retry, .chat-tray-progress)aus. Der Szenario-Namefehler-portalgilt fürconsole-errorwiefehler(erwartete 500-Meldungen ausgenommen). -
icon-svgist dauerhaft für alles unter.woms-chatausgenommen (Sprechblasen mit Anhängen zeichnet derselbe fremde Baustein). Das steht im Gatep6(Step 5) alsignore, nicht inchecks.mjs. -
Step 5: Ansichten und Gate eintragen
views.mjs bekommt die Hilfe tablet(page): ab 800 px Breite setViewportSize({ width: 1000, height: 800 }), sonst nichts. Die Schritte haben die Signatur steps(page, ctx), Anfragen liest man aus ctx.backend.calls. Prüfungen in Schritten nutzen assert aus node:assert/strict. Eine Ausnahme erzeugt den Verstoß step-failed mit Ansicht und Meldung (das Gate endet mit Exit 1). Die Kundenseiten-Ansichten tragen die IDs der Übersicht (kunde-chat, kunde-abo, kunde-gutschriften, kunde-funktionen), es gibt keine zweite Ansicht für dieselbe Reiter-URL.
| ID | Pfad / Schritte | Prüfung im Schritt |
|---|---|---|
chat-verlauf |
/customers/chat?kunde=<A> |
.chat-back bei 1440 display: none, bei 390 sichtbar. .chat-side bei 390 ausgeblendet. Stil-Sollwerte aus Task 4. |
chat-menue, chat-anhang-fehler |
wie chat-verlauf; Klick .chat-attach-btn bzw. setInputFiles (x.pdf) auf .chat-composer input[type=file]:not([accept]) |
.chat-menu sichtbar bzw. .chat-tray-item.is-error und .chat-composer-note sichtbar |
chat-tablet, gutschriften-tablet, angebote-tablet |
Ansicht mit tablet |
Chat: Liste und Verlauf nebeneinander, .chat-input-row ohne Überlauf. Tabellen: .hide-md-Spalten aus, .hide-sm-Spalten sichtbar |
chat-viel, gutschriften-viel |
Satz viel |
.chat-list.scrollHeight > clientHeight |
chat-fehler, gutschriften-fehler, angebote-fehler, speicher-fehler |
Szenario fehler-portal |
.msg.err sichtbar; bei Chat und Speicher Klick auf „Erneut versuchen“, die Seite lebt |
gutschriften-antrag |
/customers/gutschriften?antrag=<offen> |
– |
gutschriften-sperre |
wie oben; page.on('dialog', d => d.accept()); 3 Kästchen, #credit-note füllen, Doppelklick „Genehmigen & gutschreiben“; in #calc-amount 3 Ziffern mit 50 ms Abstand |
nach 1500 ms genau 1 POST …/decision und 1 POST …/calc in ctx.backend.calls (method, url) |
angebote-dialog, angebote-vorschlag, angebote-laufend |
Klick „Neues Angebot“; im Kunden-Feld „Mu“ tippen; Kunde mit laufendem Abo wählen | [role=dialog] sichtbar; letzter .autocomplete-item per elementFromPoint erreichbar; bei 390 px .dialog-foot.scrollWidth <= clientWidth |
angebote-tastatur |
page.on('dialog', d => d.accept()). Bis zu 15 Mal Tab, bis der Fokus in table liegt. Dialog öffnen, Kunden-Feld tippen, Esc, Esc. Erneut öffnen, 30 Mal Tab. Kunde wählen, „Angebot anlegen“, während der 800 ms Esc |
Fokus erreicht die Tabelle. Erstes Esc schließt nur die Liste, zweites den Dialog. Fokus bleibt im Dialog. Beim Speichern ist .dialog-close disabled und der Dialog nach Esc noch da |
Dazu die vorhandenen Ansichten chat, gutschriften, angebote, speicher, kunde-chat, kunde-abo, kunde-gutschriften, kunde-funktionen. In gates.mjs entsteht p6 nach dem Schema der Übersicht: das Gate p5 plus icon-svg für die Ansichten von P6 und aller früheren Pakete.
const P6_VIEWS = ['chat', 'chat-verlauf', 'chat-menue', 'chat-anhang-fehler', 'chat-tablet', 'chat-viel', 'chat-fehler',
'gutschriften', 'gutschriften-tablet', 'gutschriften-viel', 'gutschriften-fehler', 'gutschriften-antrag', 'gutschriften-sperre',
'angebote', 'angebote-tablet', 'angebote-fehler', 'angebote-dialog', 'angebote-vorschlag', 'angebote-laufend', 'angebote-tastatur',
'speicher', 'speicher-fehler', 'kunde-chat', 'kunde-abo', 'kunde-gutschriften', 'kunde-funktionen']
const prev = GATES.p5.scoped['icon-svg']
GATES.p6 = {
...GATES.p5,
scoped: { ...GATES.p5.scoped, 'icon-svg': { views: [...new Set([...prev.views, ...P6_VIEWS])], ignore: [...(prev.ignore ?? []), '.woms-chat'] } },
}
step-failed, external-request und console-error sind wie in jedem Gate aktiv, dark-surface, low-contrast und overflow-x kommen über GATES.p5.checks für alle Ansichten.
- Step 6: Rot-Beweis
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t2" --only chat-verlauf,angebote --gate p6; echo "exit $?"
Expected: exit 1 mit icon-svg mindestens in angebote (FaPlus). Das Bild chat-verlauf zeigt den echten Composer mit Anhängen.
Task 3: Globale Ergänzungen
Files:
- Modify:
src/styles/global.css(neuer Block am Ende)
Interfaces:
-
Produces:
.table.table-wrap td(lange Wörter brechen),.table tr[aria-current="true"] td,.dialog-foot { flex-wrap: wrap },.autocomplete .input { width: 100% }(P7 baut auf die beiden letzten). -
Step 1: Block anlegen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "aria-current|dialog-foot|\.autocomplete \.input" src/styles/global.css
Expected: zeigt, welche Regeln P1 bis P5 schon angelegt haben. Der Block steht am Dateiende, hinter allen anderen Regeln, damit er bei gleicher Spezifität gewinnt. Unter /* ===== Kunden-Unterseiten (P6) ===== */ stehen nur die fehlenden:
-
.table.table-wrap td { overflow-wrap: anywhere } -
.table tr[aria-current="true"] td { background: var(--bg-subtle) } -
.dialog-foot { flex-wrap: wrap } -
.autocomplete .input { width: 100% } -
Step 2: Bauen und Commit
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" --nur-build
Expected: Exit 0, die Bundle-Größen werden ausgegeben.
git add src/styles/global.css
git commit -m "Design P6: kleine Tabellen-, Dialog- und Autocomplete-Ergaenzungen" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 4: chat.css neu, Fehlerzeile im Verlauf
Files:
- Modify:
src/styles/chat.css(komplett neu),src/components/chat/ChatThread.jsx(Import Z. 2, Fehlerzeile Z. 120 bis 125) - Test:
tests/unit/chatCss.test.mjs(neu)
Interfaces:
-
Consumes: Tokens der Spec (
--bg,--bg-hover,--bg-subtle,--grey-bg,--text*,--line*,--accent*,--red-fg,--danger,--radius*,--ring,--shadow-pop),.msg.err,Button. -
Produces:
.woms-chatmit--chat-bubble-in,--chat-bubble-out,--chat-track; Seitenklassen.chat-page,.chat-side,.chat-list,.chat-list-item,.chat-list-top,.chat-list-last,.chat-list-time,.chat-unread,.chat-main,.chat-back. -
Step 1: Failing Test schreiben
chatCss.test.mjs liest chat.css als Quelltext (Vorbild adminRoute.test.mjs), entfernt Kommentare und zerlegt per /([^{}]+)\{/g in Selektoren (ohne @…, Kommas gesplittet). 5 Tests:
- Jeder Selektor mit
/\.chat-(bubble|meta|messages|notice|input-row|icon-btn|send|menu|tray|rec|audio|att|file|composer|drop-active)\b/beginnt mit.woms-chat(deepEqual(bad, [])). - Kein
!important. match(/@media \(max-width: 860px\)/)unddoesNotMatch(/720px/).ChatThread.jsxenthält unverändertclassName="woms-chat" ref={zoneRef},className="chat-messages" ref={boxRef}und<div ref={composerRef} />.match(/\.chat-page \.chat-back\s*\{[^}]*display:\s*none/).
- Step 2: Test laufen lassen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/chatCss.test.mjs
Expected: FAIL bei 3 (alter 720-px-Wert) und 5. Test 4 besteht schon.
- Step 3:
chat.cssneu schreiben
Der Kopfkommentar nennt: Das Portal-CSS lädt zur Laufzeit zuletzt, Gleichstand verliert, deshalb ist jede Regel strikt spezifischer und es gibt kein !important. Alle P1-Variablen und -Fixes aus P1 Task 9 Step 2 bleiben unverändert. Neu sind nur --chat-bubble-in: var(--bg-hover), --chat-bubble-out: var(--grey-bg) und --chat-track: rgba(26,31,54,.2). Maße, Abstände, Schriftgrößen und Radien folgen dem Prototyp und den Tokens der Spec und entstehen beim Bauen. Verbindlich sind die Spezifitäten und Abweichungen der Tabellen sowie die Messwerte aus Step 6.
Überschreibungen des Portal-Bausteins. Die Spalte „Eigen > Fremd“ nennt die Spezifität der eigenen Regel vor der überstimmten Portal-Regel:
| Ziel | Selektor (Eigen > Fremd) | Abweichung vom Portal-Stil |
|---|---|---|
| Container, Verlauf | .woms-chat, .woms-chat .chat-messages |
Flex-Spalte, min-width: 0. Verlauf scrollt in sich (max-height: 60vh), ohne Rand und Hintergrund. |
| Blase, Zeit | .woms-chat .chat-bubble (0,2,0 > 0,1,0), .chat-bubble.mine, .chat-meta, .chat-notice |
Fremde Blase --chat-bubble-in, eigene --chat-bubble-out (rechts), kein Rand. max-width: 78%, overflow-wrap: anywhere. Zeit und Hinweis 12 px in --text-2. |
| Datei-Chip | .woms-chat .chat-att-file (0,2,0 > 0,1,0), :hover (0,3,0 > 0,2,0) |
var(--bg), border: 0, box-shadow: var(--ring). Hover --bg-hover. |
| Audio, Fokus | .chat-audio-bar (0,2,0 > 0,1,0); .chat-att-image:focus-visible, .chat-audio-bar:focus-visible (0,3,0 > 0,2,0); .chat-bubble.mine .chat-audio-play (0,4,0 > 0,3,0); .chat-audio-dl:hover (0,3,0 > 0,2,0); .chat-audio.is-error .chat-audio-time (0,4,0 > 0,3,0) |
Leiste --chat-track. Fokusring 2px solid var(--accent) statt weiß. Abspielen --chat-accent mit --chat-on-accent. Download-Hover --accent-soft. Fehlerzeit --red-fg. |
| Eingabefeld | .woms-chat .chat-input-row textarea (0,2,1 > 0,1,1), :focus (0,3,1), Media max-width: 640px |
border: 0, box-shadow: var(--ring), font: inherit. Fokus 0 0 0 1px var(--accent), 0 0 0 4px var(--accent-ring) ohne outline. Handy font-size: 16px (gegen das iOS-Zoomen). Höhe und Radius des Portals bleiben. |
| Icon-Knöpfe, Senden | .chat-icon-btn (0,2,0 > 0,1,0), :hover:not(:disabled) (0,4,0 > 0,3,0), [aria-expanded="true"] (0,3,0 > 0,2,0), .chat-icon-btn:disabled, .chat-send:disabled (0,3,0 > 0,2,0), .chat-send.is-busy:disabled (0,4,0 > 0,3,0) |
border: 0, box-shadow: var(--ring), var(--bg). Hover --bg-hover. Offen --accent-soft mit 1-px-Akzentring. Deaktiviert opacity: .55; cursor: not-allowed, beschäftigt opacity: .8; cursor: progress. Senden bleibt über die P1-Variablen schwarz mit weißer Schrift. |
| Aufnahme | .chat-rec .chat-icon-btn (0,3,0 > 0,2,0), :hover (0,4,0 > 0,3,0); .chat-rec-stop, .chat-rec-wave (hinter der Icon-Knopf-Regel), .chat-rec-dot (0,2,0 > 0,1,0) |
Knöpfe ohne Ring und Hintergrund, Hover --accent-soft. Farbe bzw. Punkt --danger. |
| Anhang-Menü | .chat-menu (0,2,0 > 0,1,0); .chat-menu button (0,2,1 > 0,1,1), :hover, :focus-visible (0,3,1 > 0,2,1) |
Menü ohne Rand, mit --shadow-pop. Hover und Fokus --bg-hover, Fokus zusätzlich outline: 2px solid var(--accent); outline-offset: -2px (die Fremdregel setzt outline: none). |
| Vorschau, Hinweise | .chat-tray-item (0,2,0 > 0,1,0); .chat-tray-item.is-error .chat-file-size (0,4,0 > 0,3,0); .chat-composer-note (0,2,0 > 0,1,0), .is-info (0,3,0 > 0,2,0) |
Vorschau --bg-hover mit transparentem Rand. Die rote Fehlervariante der Fremdregel (gleiche Spezifität, später geladen) bleibt absichtlich. Fehlertexte --red-fg, Info --text-2. |
| Ablegen | .woms-chat.chat-drop-active::after (0,2,1 > 0,1,1) |
helle Fläche rgba(255,255,255,.92), Schrift --text, Rand --text-3 |
Seitenlayout ohne Präfix. Es folgt dem Prototyp, verbindlich sind:
-
.chat-pageist ein Grid mit 320 px für die Liste undminmax(0, 1fr)für den Verlauf. Beimax-width: 1100pxist die Liste 260 px breit. -
.chat-sideund.chat-mainhabenmin-width: 0. Zwischen ihnen steht eine senkrechte Haarlinie (--line). -
.chat-listscrollt in sich (max-height: 70vh; overflow-y: auto)..chat-list-itemist eine Haarlinien-Zeile ohne Kasten und Rand, Hover--bg-subtle,[aria-current="true"]--accent-soft. -
.chat-list-lastkürzt mit Ellipsis..chat-unreadist eine kleine Zahl in--accent, fett, 12 px, ohne Pille. -
.chat-page .chat-back { display: none }, Spezifität (0,2,0) >.btn(0,1,0). -
Bei
max-width: 860px(nicht 720 px): einspaltig..chat-page.has-thread .chat-sideund.chat-page:not(.has-thread) .chat-mainsinddisplay: none, Seite und Haupt ohne Rand und Innenabstand,.chat-page .chat-back { display: inline-flex },.woms-chat .chat-bubble { max-width: 90% }. -
Step 4: Fehlerzeile im Verlauf
In ChatThread.jsx bleibt der Zeilencontainer flex items-center gap-2 wrap. Der Text wird <span className="msg err" role="alert">, der Knopf <Button onClick={…}>Erneut versuchen</Button> ohne Icon. Der Import react-icons/fa6 entfällt. Container, Effekte und chat-notice muted bleiben.
- Step 5: Test grün
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/chatCss.test.mjs tests/unit/loadChatComposer.test.mjs
Expected: PASS.
- Step 6: Gegen das echte Fremd-CSS prüfen
chat-verlauf bekommt einen Schritt, der getComputedStyle liest und mit assert.equal vergleicht:
.chat-bubble.theirsbackground-color=rgb(246, 248, 250),.chat-bubble.mine=rgb(235, 238, 241)..chat-sendbackground-color/color=rgb(17, 17, 17)/rgb(255, 255, 255)..chat-attach-btnborder-top-width=0px,box-shadow≠none..chat-att-filebackground-color=rgb(255, 255, 255),border-top-width=0px..chat-audio-barbackground-color=rgba(26, 31, 54, 0.2)..chat-input-row textareaborder-top-width=0px.
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t4" --only chat-verlauf,chat-menue,chat-anhang-fehler --gate p6; echo "exit $?"
Expected:
-
Schritt-Prüfungen bestehen (kein
step-failed). Der Bericht enthält höchstensicon-svgam alten Zurück-Pfeil vonCustomerChatPage(Task 5). Nur dann istexit 1hier erlaubt. -
chat-verlaufbei 1440 px: Blasen hellgrau, Senden schwarz mit weißer Schrift, Icon-Knöpfe weiß mit Ring, Datei-Chip weiß. -
chat-anhang-fehler: rote Fehlerzeile und rot umrandete Vorschau. -
Step 7: Commit
git add src/styles/chat.css src/components/chat/ChatThread.jsx tests/unit/chatCss.test.mjs
git commit -m "Design P6: chat.css neu (Praefix .woms-chat, Umschaltung bei 860 px), Fehlerzeile ohne Icon" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 5: Chat-Seite
Files:
- Modify:
src/pages/CustomerChatPage.jsx(Z. 3, 61 bis 110),src/styles/global.css(KOMPAT-Regel.side-countlöschen)
Interfaces:
-
Consumes:
PageHeader,Card,Button,EmptyState,AutocompleteInput(Klasseinput),.msg.err, Klassen aus Task 4,lastLine,sortChats. -
Produces: Seite und Adresse
/customers/chat?kunde=<id>unverändert. -
Step 1: Seite umbauen
-
react-icons,size="sm"undCard pad={false}um die Liste entfallen. Die Liste ist.chat-listdirekt unter dem Suchfeld. -
Das Suchfeld bleibt
AutocompleteInput(„Neue Nachricht an Kunde…“,onPicköffnet nur die volle Beschriftung). -
Fehlerzeile:
div.flex.items-center.gap-2.wrapmitspan.msg.err[role=alert]undButton„Erneut versuchen“. Der Leerzustand „Noch keine Unterhaltungen“ bleibtEmptyState. -
Jede Unterhaltung ist ein
button.chat-list-itemmitaria-current={c.customerId === kunde ? 'true' : undefined}. Zeile 1 (.chat-list-top):<strong>Name und.chat-list-time. Zeile 2:.chat-list-lastmitlastLine(c)und beiunreadFromCustomer > 0span.chat-unread. -
Der Zurück-Knopf steht vor der Karte:
<Button className="chat-back" onClick={() => setParams({})}>← Alle Unterhaltungen</Button>, darunter<Card title={nameOf(kunde)}><ChatThread … /></Card>. Ohne Auswahl bleibt die Karte mitEmptyState(„Unterhaltung links auswählen“ / „Oder oben eine neue Nachricht an einen Kunden beginnen.“) und ohne Titel. EineCardohne Titel und ohne Aktionen rendert keinh2(Übersicht). -
Step 2:
.side-countlöschen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "side-count" src
Expected: nur noch die KOMPAT-Regel in global.css. Sie wird gelöscht (P6 ist ihr letzter Nutzer), danach 0 Treffer.
- Step 3: Prüfen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t5" --only chat,chat-verlauf,chat-tablet,chat-viel,chat-fehler,kunde-chat --gate p6
Expected: Exit 0. Bei 390 px zeigt chat nur die Liste ohne Überlauf, chat-verlauf nur den Verlauf mit „← Alle Unterhaltungen“. Bei 1440 px zwei Spalten mit senkrechter Trennlinie, ohne Zurück-Knopf. chat-tablet: Liste 260 px.
- Step 4: Commit
git add src/pages/CustomerChatPage.jsx src/styles/global.css
git commit -m "Design P6: Chat-Liste als Haarlinien-Zeilen, Zaehler und Zurueck-Pfeil als Text, side-count entfaellt" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 6: Gutschriften-Liste
Files:
- Modify:
src/pages/CreditsPage.jsx(Z. 10 bis 12),src/components/credits/CreditsView.jsx(Z. 25 bis 29),src/components/credits/CreditList.jsx(Z. 2, 7, 24 bis 62)
Interfaces:
-
Consumes:
Tabs,Badge,EmptyState;.table,.table-wrap,.hide-sm,.hide-md,.right,.num,.cell-title,.cell-sub,.link,.loading,.msg.err,.table-foot; Helfer aus Task 1. -
Produces:
CreditList({ customerId, selectedId, onSelect, reloadKey })mit unveränderten Props. -
Step 1:
CreditListumbauen -
Filter
<Tabs tabs={[{ id: 'open', label: 'Offen' }, { id: 'all', label: 'Alle' }]} …/>. State, Effekt undlistCreditsbleiben. -
Laden
div.loading„Wird geladen …“, Fehlerdiv.msg.err[role=alert], leerEmptyStatemit „Keine offenen Anträge.“ bzw. „Noch keine Anträge.“. -
table.table.table-wrapmit denselben Spalten und Zellinhalten wie heute.<br />plusarea-mutedwird.cell-sub, der Kundenname.cell-title. Klassen aufthundtd: Einganghide-sm, Leistung / bisher beihide-md, Rechnunghide-sm right num, Bezahlt bishide-md. -
Statuszelle:
Badgeund, wenn nicht leer,.cell-submitcreditStatusNote(r). Letzte Zelle ohne Kopftext:<button type="button" className="link" onClick={() => onSelect(r.id)}>{creditActionLabel(r.status)}</button>. -
Die Zeile der offenen Gutschrift trägt
aria-current="true". ImportsFaSpinner,Card,admin-areas.cssund die zwei Inline-Styles entfallen. -
Step 2:
CreditsViewundCreditsPage
CreditsView: Das div mit marginBottom um CreditDetail entfällt, die Liste steht in <div className={selected ? 'mt-3' : undefined}>. CreditsPage: Der Absatz „Übernahmegutschrift: …“ bleibt wörtlich als <p className="table-foot"> ohne Inline-Style.
- Step 3: Prüfen und Commit
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t6" --only gutschriften,gutschriften-tablet,gutschriften-viel,gutschriften-fehler,kunde-gutschriften --gate p6
Expected: Exit 0. 390 px: Kunde, Status, Aktion, kein Überlauf mit dem 60-Zeichen-Namen. 1000 px: Eingang und Rechnung sichtbar, Leistung und Bezahlt bis aus. 1440 px: alle sieben Spalten.
git add src/pages/CreditsPage.jsx src/components/credits/CreditsView.jsx src/components/credits/CreditList.jsx
git commit -m "Design P6: Gutschriften-Liste als Tabelle mit Reitern und Spaltenprioritaet" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 7: CreditDetail
Files:
- Modify:
src/components/credits/CreditDetail.jsx(Imports Z. 1 bis 9, Markup Z. 116 bis 284)
Interfaces:
-
Consumes:
Card,Badge,Button,Field;.two-col,.kv,.field-row,.check,.list-row,.cell-sub,.prose,.msg,.loading,.link;checkGlyph,checkTone. -
Produces:
CreditDetail({ id, onClose, onChanged, onOpen })unverändert. -
Step 1: Logik unverändert
Nicht angefasst werden load, figures, setFigure (300-ms-Timer), decide (decisionError, window.confirm, busyRef), apply, canApprove, amountTouched. Neu ist nur der Zustand busyAction ('', 'approve', 'question', 'reject', 'apply'), den decide und apply neben busy setzen und zurücksetzen, damit der gedrückte Knopf „Speichert …“ bzw. „Sendet …“ zeigen kann.
-
Step 2: Markup umbauen
-
Kopf:
<Card title={Antrag ${r.ref} · ${c.name || c.companyName}} actions={<Button onClick={onClose}>Schließen</Button>}>, darunterp.muted„eingegangen … “ mitBadgeundp.msg.ok[role=status]fürmessage. -
Aufbau:
div.two-colmit zwei Spalten-div. Jede Gruppe ist ein<Card title="…">mit dem heutigenh4-Text. Den Abstand zwischen den Karten einer Spalte liefert.page section + sectionaus P1, P6 baut dafür keine eigene Regel. Die Reihenfolge der Abschnitte bleibt: links Antrag, Grund / Nachricht, Nachweise, Verlauf, rechts Kunde & Vertrag, Weitere Anträge, Automatische Prüfung, Prüfung durch dich, Berechnung bzw. Entscheidung. -
Ersetzungen:
dl.area-kvwirddl.kv.- Mehrzeiler (
<br />plusarea-muted) werden.cell-sub. - Nachweise, Verlauf, Weitere Anträge und Prüfpunkte werden
.list-row. Text ausr.messageund Verlaufstexten ist.prose. - Alle Links (
domain, Nachweise) bekommenclassName="link". Die Nummer in „Weitere Anträge“ ist<button type="button" className="link" onClick={() => onOpen?.(o.id)}>statt<a href="#">. - Prüfpunkte tragen
<Badge tone={checkTone(k.status)}>{checkGlyph(k.status)}</Badge>. - „In Stripe öffnen“ ist
Button as="a" …ohne Icon,KundenansichtButtonohnevariant.
-
Formular: Die manuellen Punkte stehen als
div.fieldmit je einemlabel.check. „Berechnung“ besteht aus zwei.field-rowmitField label htmlFor(IDscalc-amount,calc-months,calc-paid-until,calc-from, Klasseninput/select).calcTextistp.strong, der Hinweisp.muted.small. „Gutschrift (€)“ (#credit-amount) und das Nachrichtenfeld (textarea#credit-note.textarea,maxLength={4000}, langes Label wörtlich) sindField. -
Knopfreihe
div.flex.items-center.gap-3.wrap:Button variant="primary"„Genehmigen & gutschreiben“ (disabled={!canApprove}),Button„Rückfrage senden“,Button variant="danger"„Ablehnen“.- Bei
busyActionzeigt der gedrückte Knopf „Speichert …“ (genehmigen, ablehnen) bzw. „Sendet …“ (Rückfrage). - „Jetzt in Stripe gutschreiben“ ist
variant="primary"mit „Speichert …“ beiapply.
-
Meldungen:
actionErroralsp.msg.err[role=alert]. Ladefehler<Card><p className="msg err" role="alert">…</p><Button onClick={onClose}>Schließen</Button></Card>(ohne Titel und Aktionen, also ohneh2). Ladendiv.loading. -
react-icons,admin-areas.cssund alle 9 Inline-Styles entfallen. -
Step 3: Prüfen und Commit
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t7" --only gutschriften-antrag,gutschriften-sperre --gate p6
Expected: Exit 0, die Sperr-Prüfung besteht (1 POST …/decision, 1 POST …/calc). gutschriften-antrag bei 1440 px zweispaltig mit genau einem schwarzen Knopf und „Ablehnen“ als rotem Text, bei 390 px einspaltig ohne Überlauf.
git add src/components/credits/CreditDetail.jsx
git commit -m "Design P6: Gutschrift-Detail zweispaltig mit Abschnitten und kv-Listen, Verhalten unveraendert" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 8: Abo-Angebote: Seite, Ansicht, Liste
Files:
- Modify:
src/pages/OffersPage.jsx(Z. 10),src/components/offers/OffersView.jsx(Z. 2, 14 bis 34),src/components/offers/OfferList.jsx(Z. 2, 7, 54 bis 118)
Interfaces:
-
Consumes:
Tabs,Badge,Button,EmptyState; Klassen wie Task 6;filterOffers,offerRowActions,offerMailLine. -
Produces:
OfferList({ customerId, reloadKey, onMessage, onChanged })undOffersView({ customerId, customerName })unverändert. -
Step 1:
OfferListumbauen -
Tabs „Offen & aktiv“ (
open) und „Alle“ (all),shown = filterOffers(offers, filter). Laden, Fehler und Leerzustand wie bei den Gutschriften („Keine offenen oder aktiven Angebote.“ / „Noch keine Angebote.“). -
table.table.table-wrapmit denselben Spalten und Zellinhalten wie heute, Mehrzeiler als.cell-sub. Klassen: Angelegthide-md, Gültig bishide-sm. Stripe-Linksa.link. -
Statuszelle:
Badge,.cell-sub„seit …“ / „wartet auf Abschluss“, „Stripe: Coupon · Abo“, danachofferMailLine(o)als.cell-sub(warnalsspan.msg.warn). -
Aktionen unter den Status-Zeilen,
div.flex.gap-3.wrap.mt-1, nur wennsendLabelodercanWithdraw:<button type="button" className="link" title="Angebots-E-Mail an … senden" onClick={() => sendMail(o)} disabled={busyId === o.id}>{sendLabel}</button>Button variant="danger"„Zurückziehen“withdrawundsendMailbleiben unverändert (2confirm, 2alert).
-
Imports
FaSpinner,Card,admin-areas.cssund die drei Inline-Styles entfallen. -
Step 2:
OffersViewundOffersPage
OffersView: Meldung als <p className="msg ok mb-2" role="status">, dann <div className="toolbar"><Button variant="primary" …>Neues Angebot</Button></div> (ohne FaPlus, auch bei offenem Dialog sichtbar), dann OfferList, am Ende {showForm && <OfferForm … />} ohne Stil-div. OffersPage: Der Absatz „Ein Angebot legt sofort …“ bleibt wörtlich als <p className="table-foot">.
- Step 3: Prüfen und Commit
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t8" --only angebote,angebote-tablet,angebote-fehler,kunde-abo --gate p6
Expected: Exit 0. 390 px: Kunde, Angebot, Status, die 50-Zeichen-E-Mail bricht, „E-Mail senden“ und „Zurückziehen“ sind erreichbar. 1440 px: alle fünf Spalten.
git add src/pages/OffersPage.jsx src/components/offers/OffersView.jsx src/components/offers/OfferList.jsx
git commit -m "Design P6: Angebote-Liste als Tabelle mit Reitern, Aktionen als Textlinks" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 9: OfferForm als Dialog
Files:
- Modify:
src/components/offers/OfferForm.jsx(Imports Z. 1 bis 8, Markup Z. 85 bis 143)
Interfaces:
-
Consumes:
Dialog({ title, onClose, busy, footer, children }),Field,Button,AutocompleteInput(Propid),.field-row,.check,.msg.err. -
Produces:
OfferForm({ fixedCustomerId, fixedCustomerName, onCreated, onCancel })unverändert.onCancelist zugleichonClosedes Dialogs. -
Step 1: Markup als Dialog
Kontext-Effekt, evaluation, save (window.confirm(offerConfirm(…))), suggestions und onPick bleiben. Der Rückgabewert wird <Dialog title="Neues Angebot" onClose={onCancel} busy={busy} footer={…}> ohne <form>. Felder in der heutigen Reihenfolge und mit den heutigen Labeltexten, alle als Field mit neuen IDs offer-* für die Label-Bindung:
- „Kunde“: mit
fixedCustomerId<p className="strong">{fixedCustomerName || 'Kunde'}</p>, sonstAutocompleteInput id="offer-customer". .field-row„Produkt“ (select.select) und „Preis pro Monat im Angebot (€)“ (input.input)..field-row„für“ (select.select) und „Angebot gültig bis“ (input.input type="date").- „Hinweis für den Kunden …“ und „Interne Notiz …“ (
input.input,maxLength500 bzw. 2000). label.check„Kunden per E-Mail über das Angebot informieren“.- Vorschau: Fehlerzeilen
p.msg.err, Vorschauzeilen alsp, die Platzhaltertexte wörtlich. Beierrorp.msg.err[role=alert].
footer: Button variant="primary" type="button" (onClick={save}, disabled={!evaluation || evaluation.errors.length > 0 || busy}) mit busy ? 'Speichert …' : evaluation?.saveLabel || 'Angebot anlegen' und Button type="button" „Abbrechen“ (onClick={onCancel}, disabled={busy}). FaSpinner, Card, admin-areas.css, die vier Inline-Styles und die Klasse offer-form entfallen.
- Step 2: Clipping und Tastatur prüfen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t9" --only angebote-dialog,angebote-vorschlag,angebote-laufend,angebote-tastatur,kunde-abo --gate p6
Expected: Exit 0, alle Schritt-Prüfungen bestehen. angebote-vorschlag: Liste vollständig erreichbar. angebote-laufend bei 390 px: Der lange Speichern-Text bricht in eine zweite Zeile der Fußzeile. angebote-tastatur: Esc schließt erst die Liste, dann den Dialog, beim Speichern wirken „ד und Esc nicht.
Besteht die Clipping-Prüfung nicht, bekommt .dialog-body .autocomplete-list im Block aus Task 3 max-height: 200px (nie !important, die Komponente bleibt unberührt).
- Step 3: Commit
git add src/components/offers/OfferForm.jsx
git commit -m "Design P6: Angebots-Formular als Dialog (Esc, Sperre beim Speichern, Vorschlagsliste geprueft)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
War die Abhilfe nötig, kommt src/styles/global.css ausdrücklich in dieses git add.
Task 10: E-Mail-Speicher
Files:
- Modify:
src/pages/StorageTasksPage.jsx(Imports Z. 1 bis 8, Markup Z. 38 bis 98)
Interfaces:
-
Consumes:
PageHeader,Card,Badge,Button,EmptyState;.table.table-wrap,.hide-sm,.hide-md,.cell-sub,.link,.msg,.list-row,.table-foot,.loading;storageDue. -
Produces: Seite
/customers/email-speicherunverändert. -
Step 1: Markup umbauen
-
Fehler
div.flex.items-center.gap-2.wrapmitspan.msg.err[role=alert]undButton„Erneut versuchen“ (onClick={load}). Ladendiv.loading„Wird geladen …“. -
Zusammenfassung
<p className={summary.warn ? 'msg warn mb-2' : 'muted mb-2'}>. -
Bei null Aufgaben
EmptyState„Keine offenen Speicher-Aufgaben.“, sonsttable.table.table-wrapmit den heutigen Spalten und Zellinhalten, Mehrzeiler als.cell-sub. Klassen: Fällighide-sm, Bestellthide-md. -
Fällig-Zelle:
Badge tone={due.tone}mitdue.labelunddue.sub(.cell-sub, wenndue.subMuted). KundennameLink className="link"auf/customers/${t.customerId}. Die Teilfehler-ZeilepartialText(t)istspan.msg.err. -
Letzte Zelle ohne Kopftext:
<button type="button" className="link" data-storage-done={t.id} onClick={() => done(t)} disabled={busyId === t.id}>{busyId === t.id ? 'Speichert …' : 'Erledigt'}</button>. -
„Gescheiterte Änderungen (letzte 30 Tage)“ bleibt
Card title, die Zeilen sind.list-row(TextfailedTextalsspan.muted). Der lange Hinweisabsatz bleibt wörtlich als<p className="table-foot">. -
done,window.confirm('Stripe-Menge von Hand korrigiert?')undwindow.alertbleiben.react-icons,admin-areas.cssund beide Inline-Styles entfallen. -
Step 2: Prüfen und Commit
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t10" --only speicher,speicher-fehler --gate p6
Expected: Exit 0. 390 px: Kunde, Aufgabe und „Erledigt“ ohne Überlauf. 1440 px: alle fünf Spalten, Fällig als Badge mit Datum.
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -c "data-storage-done" src/pages/StorageTasksPage.jsx
Expected: 1.
git add src/pages/StorageTasksPage.jsx
git commit -m "Design P6: E-Mail-Speicher als Tabelle, Erledigt als Textlink mit data-storage-done" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 11: admin-areas.css löschen, KOMPAT-Reste
Files:
- Delete:
src/styles/admin-areas.css - Modify:
src/styles/global.css(KOMPAT-Zeilen.credit-cols a,.area-rows a)
Interfaces:
-
Consumes:
$W/css-klassen.mjsaus P1. P4 hat die.funktionen-*-Regeln nachglobal.cssverlegt, die Tasks 6 bis 10 haben die Importzeilen der P6-Dateien entfernt. -
Produces: keine Datei
admin-areas.css, keinen Import und keine Klasse aus ihr. -
Step 1: Restnutzer ermitteln
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "\barea-|credit-(row|cols|files|checks|history|manual|calc|field)|offer-(row|form)|storage-row" src --glob '!*.css'; rg -n "admin-areas" src
Expected: keine Treffer, auch nicht in CustomerFunktionen.jsx und AboStatusCard.jsx. Ein Treffer in einer P6-Datei wird dort behoben. Ein Treffer in einer P4-Datei heißt, dass P4 sie nicht vollständig gelöst hat: stoppen und melden. Das Muster enthält bewusst kein funktionen-, denn diese Klassen leben in global.css.
- Step 2: Datei löschen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git rm -q src/styles/admin-areas.css && rg -n "admin-areas" src
Expected: keine Treffer.
- Step 3: KOMPAT-Reste löschen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "credit-cols|area-rows|side-count" src/styles
Expected: genau die beiden Zeilen in global.css (KOMPAT „Links in Bestandsklassen“) für .credit-cols a und .area-rows a. Beide werden gelöscht, .side-count ist schon seit Task 5 weg. Danach liefert derselbe Befehl keine Treffer.
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 die in P1 erlaubten Namen (p-4, btn-sm, status-filter, supporters-editor, timeline, Bootstrap-Namen in WorksheetList.jsx). Meldet das Skript die gelöschte Datei als fehlend, wird sie in der Dateiliste von css-klassen.mjs (unter $W) gestrichen.
- Step 4: Bauen und Commit
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6-t11" --only kunde-abo,kunde-gutschriften,kunde-funktionen,gutschriften,angebote,speicher --gate p6
Expected: Exit 0. Die Kundenseiten-Reiter kunde-abo und kunde-funktionen sehen aus wie nach P4 (Vergleich mit $W/shots/p4), die drei Listen wie nach Task 6, 8 und 10.
git status --short
git add src/styles/global.css
git commit -m "Design P6: admin-areas.css geloescht, KOMPAT-Reste entfernt" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
git status --short vor dem Commit darf nur D src/styles/admin-areas.css (vom git rm schon vorgemerkt) und M src/styles/global.css zeigen. Die Löschung steht damit im selben Commit.
Task 12: Gate P6, Review, Tag, Übergabe
- Step 1: Rebase und Ausgangswerte
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git fetch origin && git rebase origin/test && "$W/check.sh"
Expected: Exit 0, # fail 0, # pass = N5 + 15 (10 Lib-Tests, 5 chatCss). Liegen neue Commits auf test, ist die Zahl entsprechend höher und wird notiert. Die Bundle-Größen aus der Ausgabe werden mit P5 verglichen, das CSS sollte kleiner sein (admin-areas.css ist weg).
- Step 2: Vollständiger Gate-Lauf
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p6 && "$W/shots.sh" p6 "$W/shots/p6" --gate p6
Expected: Exit 0, in allen Ansichten kein Verstoß gegen dark-surface, low-contrast, overflow-x, console-error, external-request, step-failed. In den P6-Ansichten dazu kein icon-svg außerhalb von .woms-chat.
- Step 3: Inventur und Hygiene
Run:
source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && for f in src/components/credits/CreditDetail.jsx src/components/offers/OfferList.jsx src/components/offers/OfferForm.jsx src/pages/StorageTasksPage.jsx; do echo "$f $(rg -o 'window\.confirm|window\.alert|busyRef|data-storage-done|id="(calc|credit)-[a-z-]+"' $f | wc -l)"; done
rg -n "react-icons|size=\"sm\"|style=\{\{|!important" src/pages/{CustomerChat,Credits,Offers,StorageTasks}Page.jsx src/components/{chat,credits,offers} src/styles/chat.css
rg -n "admin-areas" src
git diff --stat design-p5..HEAD -- src/lib/loadChatComposer.js tests/unit/loadChatComposer.test.mjs
test ! -e src/styles/admin-areas.css && echo "admin-areas.css weg"
Expected: Zählung 16, 4, 1, 3. Die Befehle zwei bis vier sind leer, am Ende steht „admin-areas.css weg“.
- Step 4: Code-Review (sonnet)
Geprüft wird der Diff design-p5..HEAD gegen die Spec-Abschnitte „Bausteine“, „Seiten ohne Prototyp-Vorlage“ und P6. Schwerpunkte:
- Jede Chat-Regel hat Präfix und strikt höhere Spezifität (Tabelle in Task 4).
admin-areas.cssist gelöscht, kein Import und keine ihrer Klassen ist übrig,.funktionen-*stehen unverändert inglobal.css.CreditDetailverhält sich unverändert (busyRef, Debounce,confirm).- Der Textdiff (
git diff design-p5..HEAD -w) zeigt nur Textänderungen, die die Spec unter „Texte“ erlaubt (darunter „Speichert …“, „Sendet …“, „Wird geladen …“) oder die ein Icon durch ein Zeichen ersetzen (z. B. „← Alle Unterhaltungen“). - Jede Aktion der alten Seiten ist erreichbar: Prüfen/Ansehen, Schließen, Kundenansicht öffnen, In Stripe öffnen, Weitere Anträge, Genehmigen, Rückfrage, Ablehnen, Jetzt in Stripe gutschreiben, Neues Angebot, E-Mail senden/Erneut senden, Zurückziehen, Coupon- und Abo-Link, Erledigt, Kundenlink, Erneut versuchen, Neue Nachricht an Kunde, Unterhaltung öffnen, Zurück.
Wichtige Befunde werden behoben. Danach kommt der Rebase aus Step 1 noch einmal und Step 2 als Kurzlauf mit --only chat-verlauf,gutschriften,angebote,speicher.
- Step 5: Tag und Übergabe
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" && git tag design-p6 && git log --oneline origin/test..design-p6 && git rev-parse design-p6
Der Eigentümer bekommt:
-
die Screenshots aus
shots/p6/1440x900undshots/p6/390x844. Auswahl:chat,chat-verlauf,chat-menue,chat-anhang-fehler,chat-tablet,gutschriften,gutschriften-antrag,gutschriften-tablet,angebote,angebote-dialog,angebote-vorschlag,speicher,kunde-chat,kunde-abo. -
die bewusst offenen Punkte:
- Lightbox und Kamera bleiben dunkel, „Foto verwenden“ bleibt im Portal-Lila (nicht aus WOMS änderbar).
- Die Anhang-Menü-Icons des Portal-Bausteins bleiben.
- Die Blasenfarben
--bg-hoverund--grey-bgsind auf Weiß sehr zart. Die Stellschrauben sind--chat-bubble-inund--chat-bubble-outinchat.css.
-
den Hinweis zu tp2:
admin-areas.cssist gelöscht. Ändertfeature/terminplaner-tp2die Datei (Anleitungs-Panel der Funktionen), gibt es beim Zusammenführen einen Konflikt, und die Panel-Regeln gehören dann in den Block „Kunden (P4)“ vonglobal.css. -
die Push-Zeile
git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p6^{commit}:refs/heads/test' -
die Rücknahme
git revert --no-commit design-p5..design-p6als Commit auftest, durch den Eigentümer. Sie gilt nur, solange P6 das jüngste Paket ist. -
Step 6: Nach dem Push (erst, wenn der Eigentümer gepusht hat)
-
Im Log von
webklar-ticket-buildsteht „Deployment completed successfully“. -
curl -s https://ticket.webklar.com/ | grep -o 'assets/index-[^"]*\.js'stimmt mitdist/index.htmldes Tags überein. -
Lesender Sichttest im Chrome des Eigentümers:
/customers/chatmit geöffnetem Verlauf (Anhang-Menü öffnen),/customers/gutschriftenund/customers/angebote.