31 KiB
WOMS „Weißes Design“ P3: Planboard – Umsetzungsplan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Nach P3 zeigt das Planboard eine Wochenansicht ohne Karten:
- sieben Tagesspalten mit Haarlinien, Wochenende in
--bg-subtle, heutiger Tag als schwarze Datumspille - Termine als Links auf die Ticketseite, Akquise-Termine hell orange, stornierte Termine weiter sichtbar
- Kopf „Kalenderwoche N · Datum bis Datum · n Termine“ mit den Aktionen ← · Heute · →
- unter 860 px eine Spalte
Die Logik in lib/planboard.js bleibt unverändert.
Architecture:
.week,.day,.apptund ihre Unterklassen kommen als eigener Block „Planboard (P3)“ insrc/styles/global.css, vor den KOMPAT-Block. Die Werte stammen ausquelle/styles.css(Zeilen 379–397 und 535–537).- Die reine Anzeigelogik (Kopfzeile, Terminzeile, Klassen, Linkziel) steht in der neuen
src/lib/planboardView.jsmit Tests.PlanboardPage.jsxbesteht danach nur noch aus Markup. - Die Seite nutzt
PageHeaderundButtonaus P1 sowieticketPathundticketRefaus P2. Sie enthält keine Icons und keine Inline-Styles.
Tech Stack: React 18, react-router-dom 6, date-fns 2.30 (de, getISOWeek), reines CSS. Tests mit node --test, Screenshot-Werkzeug aus P0 (Playwright 1.62, page.clock).
Spec: docs/superpowers/specs/2026-10-04-weisses-design-design.md, Abschnitte „Gestaltungsregeln“, „Bausteine“ (Seitenkopf, Knöpfe, Links), „Ticket als eigene Seite / Route und Links“, „Texte“ und Paket P3. Detail im Anhang 2026-10-04-weisses-design-umbaukarte.md, Abschnitt 5 (P3), 3 und 8.1 (Punkt 8). Globale Regeln und gemeinsame Schnittstellen: 2026-10-05-weisses-design-00-uebersicht.md.
Global Constraints
Jeder Run-Block beginnt mit source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh &&. Danach gelten $WT (Worktree, aktuelles Verzeichnis), $W (Werkzeug) und $TMPDIR. Tests und Build laufen nur über "$W/check.sh", Browser-Läufe nur über "$W/shots.sh" (Regeln der Übersicht).
Es gelten die Regeln der Übersicht. Für P3 kommt hinzu:
- Zeilenangaben gelten für den Stand
603d08c. Nach P1 bis P2 können sie leicht verschoben sein, maßgeblich sind die genannten Namen. - Voraussetzung ist das Tag
design-p2mitsrc/lib/ticketRoutes.js(ticketPath,ticketRef). - Geändert werden nur
src/pages/PlanboardPage.jsxundsrc/styles/global.css(ein neuer Block). Neu sindsrc/lib/planboardView.jsund zwei Testdateien.src/lib/planboard.js,useWorkordersunduseEmployeesbleiben unverändert. - Sichtbare Texte bleiben wörtlich: „Planboard“, „Heute“, „Keine Termine“, „ohne Uhrzeit“, „niemand zugewiesen“, Tooltip „Ticket #N öffnen“. Neu ist nur, was die Spec unter „Planboard-Kopf“ nennt: die Kopfzeile. Die
aria-label-Texte „Vorherige Woche“ und „Nächste Woche“ sind nicht sichtbar. - Erreichbar bleiben alle heutigen Bedienelemente: Wochenwechsel (zwei Pfeilknöpfe), „Heute“ und jeder Termin als Link auf sein Ticket. Es gibt keine
data-testidund keinewindow.confirmin der Seite. - Entscheidung: Der Tageskopf behält die heutigen Texte (voller Wochentag, Datum als
dd.MM). Die Spec ist dort still, der Prototyp kürzt auf „Mo 5.“. Die Großbuchstaben des Wochentags setzt das CSS (Spec). Weil die Spalten bei 861–1100 px unter 120 px breit sind, darf.day-headumbrechen. Das volle Datum steht zusätzlich imaria-labelder Spalte. - Entscheidung: Kein Fußtext und keine Legende „Akquise“. Der Prototyp hat beides, die Spec nennt es nicht.
- Entscheidung: Stornierte Termine sind sichtbar und zählen im Kopf mit, ohne eigene Optik (wie heute).
- Entscheidung: Das Linkziel ist
ticketPath(ticketRef({ woid: entry.woid, $id: entry.id })), also diewoidund ohne sie die$id. Die Zeile eines Termins ohnewoidzeigt kein leeres „#“ mehr, sondern nur den Mitarbeiter, sein Tooltip lautet „Ticket öffnen“. - Entscheidung: Während die Tickets laden, endet die Kopfzeile auf „Wird geladen …“ statt auf einer Terminzahl.
- Entscheidung: Akquise wird wie heute über
String(type).toLowerCase() === 'akquise'erkannt. Die grünen und grauen Typfarben entfallen, alle anderen Termine bekommen--bg-hover. - Entscheidung: Der Inhalt eines Termins behält die heutige Reihenfolge (Zeit, Kunde oder Betreff, Betreff, Nummer · Mitarbeiter), damit keine Information wandert.
- Entscheidung: Auf Wochenendspalten bekommen Termine einen weißen Grund, sonst verschwinden sie im
--bg-subtle.
Review Focus
Alle fünf Punkte der Übersicht treffen das Planboard. Jede Prüfung hat eine Ansicht in Task 4.
- Altdaten: Ansicht
planboard-altdaten.2.5.2026erscheint am Samstag der Woche 18.31.02.2026, ein ISO-Datum und ein leeres Datum ergeben weder einen Eintrag noch einen Fehler.- Ein Ticket ohne
woidverlinkt über die$idund zeigt kein leeres „#“. - Im Text steht weder
undefinednochnullnochNaN.
- Lange Inhalte: Ansicht
planboard. Am Samstag stehen ein Kundenname mit 60 und ein Betreff mit 120 Zeichen. Es gibt kein waagerechtes Scrollen (overflow-x), und kein.dayist breiter als seine Spalte (scrollWidth <= clientWidth). - Teilausfall: Ansicht
planboard-fehler. Antwortet die Mitarbeiterliste mit 500, erscheinen alle 13 Termine. Der Mitarbeiter steht dann ausassignedName. - Nur Tastatur: Ansicht
planboard-tastatur. Der erste Termin ist per Tab erreichbar, trägt den schwarzen Fokusring und öffnet mit Enter/tickets/100201. - Viele Einträge: Der Donnerstag hat sechs Termine. Die Spalte wächst nach unten, nichts überlappt, die anderen Spalten bleiben unverändert hoch.
Task 1: Paketstart und Ausgangswerte
Files: keine Änderung (nur Lesen und Prüfen).
Interfaces:
-
Consumes: Tag
design-p2,ticketPath(ref, tab)undticketRef(ticket)aussrc/lib/ticketRoutes.js. -
Produces: die Ausgangs-Testzahl N0 für die Übergabe.
-
Step 1: Paketstart
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git fetch origin && git merge-base --is-ancestor design-p2 HEAD && git rebase origin/test && "$W/check.sh" && git status --short
Expected:
-
Alle Befehle enden mit Exit 0,
git statusgibt keine Zeile aus. Schlägtmerge-basefehl, istdesign-p2nicht im Branch: Stopp und melden, P3 darf erst nach P2 laufen. -
check.shzeigt# fail 0. Die Zahl# passist N0 (Soll nach P3: N0 + 9). -
Step 2: Parallele Arbeit an
feature/terminplaner-tp2
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git log --oneline origin/test..feature/terminplaner-tp2 -- src/pages/PlanboardPage.jsx src/lib/planboard.js src/lib/planboardView.js src/styles/global.css
Expected: keine Ausgabe. Gibt der Befehl Zeilen aus, stoppt der Agent und fragt den Eigentümer. Er empfiehlt, tp2 zuerst nach test zu bringen.
- Step 3:
ticketPathundticketRefsind da
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "export function (ticketPath|ticketRef)" src/lib/ticketRoutes.js
Expected: zwei Treffer. Sonst Stopp: P3 darf erst nach P2 laufen.
- Step 4: Stand der Seite und des CSS
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "ticketPath|react-icons|style=\{\{" src/pages/PlanboardPage.jsx; rg -n "^\s*\.(week|day|appt)\b" src/styles/global.css
Expected:
- In der Seite: ein Treffer
ticketPath(P2 hat die Linkzeile umgestellt), dazu die Icon-Importe und die Inline-Styles. - Im CSS: keine Treffer. Findet P1 dort schon Mobil-Regeln für
.weekoder.day, werden sie in Task 5 Step 1 in den P3-Block verschoben.
Task 2: Charakterisierungstests für lib/planboard.js
Die Logik hat bisher keinen Test. P3 benutzt sie unverändert, die Altwerte aus Review Focus 1 werden jetzt festgeschrieben.
Files:
- Test:
tests/unit/planboard.test.mjs(neu)
Interfaces:
-
Consumes:
parseGermanDate,formatTime,dayKey,groupAppointmentsByDay,countInRangeaussrc/lib/planboard.js, unverändert. -
Produces: vier Tests.
-
Step 1: Tests schreiben
const { parseGermanDate, formatTime, dayKey, groupAppointmentsByDay, countInRange } = await loadModule('src/lib/planboard.js')
test('parseGermanDate: ein- und zweistellig, Altwerte ergeben null', () => {
assert.equal(parseGermanDate('2.5.2026').getTime(), new Date(2026, 4, 2).getTime())
assert.equal(parseGermanDate(' 07.10.2026 ').getTime(), new Date(2026, 9, 7).getTime())
for (const v of ['31.02.2026', '0.1.2026', '2026-10-07T09:00:00.000Z', '2026-10-07', '7.10.26', '', undefined, null, 'morgen']) {
assert.equal(parseGermanDate(v), null, String(v))
}
})
test('formatTime: hhmm und hh:mm, ungültige Werte ergeben leer', () => {
for (const [v, out] of [['0900', '09:00'], ['900', '09:00'], ['9:00', '09:00'], ['09:30', '09:30'], ['2359', '23:59']]) {
assert.equal(formatTime(v), out, v)
}
for (const v of ['2400', '1260', '9', 'abc', '', undefined]) assert.equal(formatTime(v), '', String(v))
assert.equal(dayKey(new Date(2026, 0, 5)), '2026-01-05')
})
const employees = [{ userId: 'u1', displayName: 'Kenso Grimm', shortcode: 'KNSO' }]
const workorders = [
{ $id: 'a', woid: '1', startDate: '7.10.2026', startTime: '1400', endTime: '1500', topic: 'Spät', type: 'Webpage', status: 'Open', customerName: 'Alpha', assignedTo: 'u1' },
{ $id: 'b', woid: '2', startDate: '07.10.2026', startTime: '', topic: 'Ohne Uhrzeit', type: 'Akquise', status: 'Open', assignedName: 'Ehemalige' },
{ $id: 'c', woid: '3', startDate: '7.10.2026', startTime: '0830', topic: 'Früh', type: 'Webpage', status: 'Cancelled' },
{ $id: 'd', startDate: '8.10.2026', startTime: '1000', title: 'Nur Titel' },
{ $id: 'h', startDate: '9.10.2026' },
{ $id: 'e', woid: '5', startDate: '31.02.2026', startTime: '1000', topic: 'Ungültig' },
{ $id: 'f', woid: '6', startDate: '2026-10-07T09:00:00.000Z', topic: 'ISO' },
{ $id: 'g', woid: '7', startDate: '', topic: 'Leer' },
]
test('groupAppointmentsByDay: nach Uhrzeit, ohne Uhrzeit am Ende, Stornierte bleiben, Altwerte fallen weg', () => {
const byDay = groupAppointmentsByDay(workorders, employees)
assert.deepEqual(Object.keys(byDay).sort(), ['2026-10-07', '2026-10-08', '2026-10-09'])
assert.deepEqual(byDay['2026-10-07'].map((a) => a.id), ['c', 'a', 'b'])
assert.equal(byDay['2026-10-07'][0].status, 'Cancelled')
assert.equal(byDay['2026-10-07'][1].employeeShort, 'KNSO')
assert.equal(byDay['2026-10-07'][1].employeeName, 'Kenso Grimm')
assert.equal(byDay['2026-10-07'][2].employeeName, 'Ehemalige')
assert.equal(byDay['2026-10-07'][2].employeeShort, '')
assert.equal(byDay['2026-10-08'][0].woid, '')
assert.equal(byDay['2026-10-08'][0].topic, 'Nur Titel')
assert.equal(byDay['2026-10-09'][0].topic, 'Ohne Betreff')
})
test('countInRange: zählt nur die genannten Tage', () => {
const byDay = groupAppointmentsByDay(workorders, employees)
const week = (day) => Array.from({ length: 7 }, (_, i) => new Date(2026, 9, day + i))
assert.equal(countInRange(byDay, week(5)), 5)
assert.equal(countInRange(byDay, week(12)), 0)
assert.equal(countInRange(byDay), 0)
})
- Step 2: Tests laufen lassen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/planboard.test.mjs
Expected: PASS, 4 Tests. Die Tests halten den Bestand fest, es ändert sich kein Produktivcode. Schlägt einer fehl, wurde das Verhalten falsch gelesen. Dann wird der Test korrigiert, nicht planboard.js.
- Step 3: Commit
git add tests/unit/planboard.test.mjs
git commit -m "Planboard: Tests halten Datums- und Terminlogik fest (Altwerte, Stornierte, ohne Uhrzeit)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 3: planboardView.js (Anzeigelogik)
Files:
- Create:
src/lib/planboardView.js - Test:
tests/unit/planboardView.test.mjs(neu)
Interfaces:
-
Consumes:
date-fns(format,getISOWeek,addDays) undde. -
Produces (Signaturen, die
PlanboardPagebenutzt):planboardSubtitle({ weekStart: Date, count: number, loading?: boolean }) → stringdayLabels(day: Date) → { weekday: string, number: string, full: string }appointmentClass(type) → 'appt' | 'appt akquise'appointmentTimeLabel({ time, endTime }) → stringappointmentOwnerLabel({ woid, employeeName, employeeShort }) → stringappointmentTitle({ woid }) → string
-
Step 1: Failing Tests schreiben
const { planboardSubtitle, dayLabels, appointmentClass, appointmentTimeLabel, appointmentOwnerLabel, appointmentTitle } =
await loadModule('src/lib/planboardView.js')
test('planboardSubtitle: Kalenderwoche, Zeitraum, Terminzahl', () => {
const mo = new Date(2026, 9, 5)
assert.equal(planboardSubtitle({ weekStart: mo, count: 13 }), 'Kalenderwoche 41 · 5. Oktober bis 11. Oktober · 13 Termine')
assert.equal(planboardSubtitle({ weekStart: mo, count: 1 }), 'Kalenderwoche 41 · 5. Oktober bis 11. Oktober · 1 Termin')
assert.equal(planboardSubtitle({ weekStart: mo, count: 0 }), 'Kalenderwoche 41 · 5. Oktober bis 11. Oktober · 0 Termine')
assert.equal(planboardSubtitle({ weekStart: mo, count: 0, loading: true }), 'Kalenderwoche 41 · 5. Oktober bis 11. Oktober · Wird geladen …')
assert.equal(planboardSubtitle({ weekStart: new Date(2026, 11, 28), count: 2 }), 'Kalenderwoche 53 · 28. Dezember bis 3. Januar · 2 Termine')
assert.equal(planboardSubtitle({ weekStart: new Date(2027, 0, 4), count: 2 }), 'Kalenderwoche 1 · 4. Januar bis 10. Januar · 2 Termine')
})
test('dayLabels: Wochentag, Datum, volles Datum', () => {
assert.deepEqual(dayLabels(new Date(2026, 9, 5)), { weekday: 'Montag', number: '05.10', full: 'Montag, 5. Oktober' })
assert.deepEqual(dayLabels(new Date(2026, 9, 11)), { weekday: 'Sonntag', number: '11.10', full: 'Sonntag, 11. Oktober' })
})
test('appointmentClass und appointmentTimeLabel', () => {
for (const t of ['Akquise', 'akquise', 'AKQUISE']) assert.equal(appointmentClass(t), 'appt akquise', t)
for (const t of ['Webpage', 'Project', 'Migration', '', undefined, null]) assert.equal(appointmentClass(t), 'appt', String(t))
assert.equal(appointmentTimeLabel({ time: '08:00', endTime: '09:30' }), '08:00–09:30')
assert.equal(appointmentTimeLabel({ time: '08:00', endTime: '' }), '08:00')
assert.equal(appointmentTimeLabel({ time: '', endTime: '09:30' }), 'ohne Uhrzeit')
assert.equal(appointmentTimeLabel({ time: '', endTime: '' }), 'ohne Uhrzeit')
})
test('appointmentOwnerLabel: Nummer und Mitarbeiter', () => {
assert.equal(appointmentOwnerLabel({ woid: '100201', employeeName: 'Kenso Grimm', employeeShort: 'KNSO' }), '#100201 · KNSO')
assert.equal(appointmentOwnerLabel({ woid: '100201', employeeName: 'Ehemalige', employeeShort: '' }), '#100201 · Ehemalige')
assert.equal(appointmentOwnerLabel({ woid: '100201', employeeName: '', employeeShort: '' }), '#100201 · niemand zugewiesen')
assert.equal(appointmentOwnerLabel({ woid: '', employeeName: 'Kenso Grimm', employeeShort: 'KNSO' }), 'KNSO')
assert.equal(appointmentOwnerLabel({ woid: '', employeeName: '', employeeShort: '' }), 'niemand zugewiesen')
})
test('appointmentTitle: Tooltip mit und ohne woid', () => {
assert.equal(appointmentTitle({ woid: '100201' }), 'Ticket #100201 öffnen')
assert.equal(appointmentTitle({ woid: '' }), 'Ticket öffnen')
})
- Step 2: Test laufen lassen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/planboardView.test.mjs
Expected: FAIL, Could not resolve "src/lib/planboardView.js" bzw. Funktionen fehlen.
- Step 3:
planboardView.jsimplementieren
Festgelegte Werte:
-
Zeitraum:
format(d, 'd. MMMM', { locale: de })fürweekStartundaddDays(weekStart, 6), Verbindung „ bis “. -
Kopfzeile:
Kalenderwoche ${getISOWeek(weekStart)} · ${von} bis ${bis} · ${rest}.restistWird geladen …beiloading, sonst${count} Termin(genau 1) oder${count} Termine. -
dayLabels:weekday=format(day, 'EEEE', { locale: de }),number=format(day, 'dd.MM'),full=format(day, 'EEEE, d. MMMM', { locale: de }). -
Zeitbereich in
appointmentTimeLabelmit Halbgeviertstrich „–“ (U+2013), wie heute. -
Die Datei importiert
date-fns. Der Test-Lader bündelt es (geprüft mitgetISOWeekunddate-fns/locale), sie darf nur keinen Appwrite-Import bekommen. -
appointmentOwnerLabel: wie heute entscheidetemployeeName, ob ein Name oder „niemand zugewiesen“ erscheint. Mitwoidsteht#woid · …, ohnewoidnur der Teil nach dem Punkt. -
Step 4: Test grün
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/planboardView.test.mjs && "$W/check.sh" --nur-tests
Expected: PASS, Exit 0, # fail 0, # pass = N0 + 9.
- Step 5: Commit
git add src/lib/planboardView.js tests/unit/planboardView.test.mjs
git commit -m "Planboard: Anzeigelogik fuer Kopfzeile, Tageskopf und Terminzeile mit Tests" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 4: Testdaten, Ansichten und Gate (Prüfung vor dem Umbau)
Alles liegt im Werkzeug außerhalb des Repos, deshalb gibt es keinen Commit.
Files (unter $W/):
- Create:
fixtures/planboard.mjs - Modify:
views.mjs,gates.mjs, bei Bedarfbackend.mjs
Interfaces:
-
Consumes:
createBackend,VIEWS,GATESaus P0. -
Produces: die Ansichten
planboard,planboard-woche-vor,planboard-leer,planboard-altdaten,planboard-fehler,planboard-tastaturund das Gatep3. -
Step 1:
fixtures/planboard.mjsanlegen
Aufbau wie altdaten.mjs: Es importiert standard.mjs und ersetzt workorders durch genau diese Tickets. Alle zugewiesenen Tickets tragen zusätzlich assignedName (voller Name). Ein Kürzel steht für assignedTo = userId des Mitarbeiters mit diesem Kürzel aus standard. Tickets „ohne woid“ haben kein Feld woid und die feste $id aus der Tabelle.
| woid | startDate | Zeit | Art | Status | Kunde | Betreff | Zuständig |
|---|---|---|---|---|---|---|---|
| 100201 | 5.10.2026 |
0900–1030 | Webpage | Occupied | Bäckerei Sonnenschein | Öffnungszeiten für die Feiertage anpassen | KNSO |
| 100202 | 06.10.2026 |
1400, ohne Ende | Akquise | In Kontakt | Gärtnerei Grünblick | Erstgespräch Rechnungsanalyse | ANDJ |
| 100203 | 7.10.2026 |
0830–0915 | Webpage | Occupied | Kanzlei Weber & Partner | Kontaktformular landet im Spam | JUSN |
| 100204 | 7.10.2026 |
ohne Zeit | Migration | Open | ohne Kunde | Umzug ohne Uhrzeit und ohne Kunde | niemand, ohne assignedName |
| 100205 | 7.10.2026 |
1500–1600 | Webpage | Cancelled | Fahrschule Roth | Storniertes Gespräch | KNSO |
| 100211–100216 | 8.10.2026 |
0800, 0900, 1000, 1300, 1400, 1530 | 100214 ist Akquise, sonst Webpage | gemischt | Testkunde 1–6 | Termin 1–6 | gemischt |
ohne woid, $id: 'ohne-woid-fr' |
9.10.2026 |
1100–1115 | Project | Open | Physiotherapie am Markt | Ticket ohne Ticketnummer | NIRE |
| 100206 | 10.10.2026 |
1000–1100 | Webpage | Occupied | Müller-Lüdenscheidt Metallbau- und Anlagentechnik GmbH & Co. (60 Zeichen) |
Betreff mit 120 Zeichen (Wert aus altdaten.mjs) |
assignedTo: 'ehemalig' (nicht in der Mitarbeiterliste), assignedName: 'Ehemalige Kollegin' |
| 100230 | 12.10.2026 |
0900–1000 | Webpage | Open | Café Mühlental | Termin nächste Woche | JUSN |
Dazu die Altwerte, die nie im Planboard erscheinen dürfen oder in einer anderen Woche liegen:
| woid | startDate | Zeit | Betreff | Erwartung |
|---|---|---|---|---|
| 100301 | 2.5.2026 |
1000–1100 | Altwert einstellig (Kunde „Altkunde GmbH“, KNSO) | erscheint am Samstag der Woche 18 |
ohne woid, $id: 'ohne-woid-alt' |
1.5.2026 |
0900 | Ticket ohne Nummer im Altbestand (Altkunde GmbH, niemand) | erscheint am Freitag der Woche 18 |
| 100302 | 31.02.2026 |
0900 | Ungültiges Datum | nie sichtbar |
| 100303 | 2026-10-07T09:00:00.000Z |
– | ISO-Datum | nie sichtbar |
| 100304 | leer | – | Ohne Datum | nie sichtbar |
Soll: In der Woche 5.–11. Oktober stehen 13 Termine (1 + 1 + 3 + 6 + 1 + 1), davon 2 Akquise (100202, 100214). In der Woche 12.–18. Oktober steht 1 Termin, in der Woche 27. April–3. Mai stehen 2.
- Step 2: Szenario
fehlerdeckt beide Mitarbeiter-Wege ab
useEmployees fragt zuerst GET https://portal.test/api/admin/employees und fällt bei einem Fehler auf die Appwrite-Sammlung employees zurück.
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "fehler" "$W/backend.mjs"
Expected: In fehler antworten beide Wege mit 500. Fehlt einer, wird er in backend.mjs ergänzt. Sonst lädt die Liste trotz Szenario, und Review Focus 3 prüft nichts.
- Step 3: Ansichten in
views.mjsergänzen
Die Schritte haben die Signatur steps(page, ctx) aus der Übersicht. Alle Ansichten setzen zuerst die Uhr (page.clock.setFixedTime, ohne die Zeit einzufrieren) und klicken dann „Heute“. Das ist nötig, weil die Seite die aktuelle Woche beim ersten Rendern bestimmt. Eine Zusicherung, die nicht stimmt, wirft einen Fehler (Verstoß step-failed, in jedem Gate aktiv).
| ID | Daten | Schritte | Zusicherung |
|---|---|---|---|
planboard |
planboard |
Uhr 2026-10-07T10:00:00+02:00, „Heute“ |
Kopf Kalenderwoche 41 · 5. Oktober bis 11. Oktober · 13 Termine. 7 .day, genau 2 .day.weekend, .day.today .dn = „07.10“, 13 a.appt, 2 a.appt.akquise, 1 Mal „Keine Termine“ (Sonntag). Je .day gilt scrollWidth <= clientWidth. Der Donnerstag hat 6 a.appt, jeder beginnt unter dem Ende des vorigen |
planboard-woche-vor |
planboard |
wie oben, dann „Nächste Woche“ | Kopf Kalenderwoche 42 · 12. Oktober bis 18. Oktober · 1 Termin |
planboard-leer |
planboard |
wie oben, zweimal „Nächste Woche“ | Kopf Kalenderwoche 43 · 19. Oktober bis 25. Oktober · 0 Termine, 7 Mal „Keine Termine“ |
planboard-altdaten |
planboard |
Uhr 2026-05-04T10:00:00+02:00, „Heute“, „Vorherige Woche“ |
Kopf Kalenderwoche 18 · 27. April bis 3. Mai · 2 Termine. Der Text enthält weder undefined noch null noch NaN. Der Freitagstermin hat einen href, der auf /tickets/ohne-woid-alt endet, und sein Text enthält kein „#“ |
planboard-fehler |
planboard, Szenario fehler |
wie planboard |
13 a.appt. Der Montagstermin zeigt „Kenso Grimm“ statt „KNSO“, der Samstagstermin „Ehemalige Kollegin“. ctx.backend.calls enthält je einen GET auf …/api/admin/employees und auf …/collections/employees/documents (beide Wege wurden versucht) |
planboard-tastatur |
planboard |
wie planboard. Tab drücken (höchstens 40 Mal), bis document.activeElement ein a.appt ist |
Die Outline des fokussierten Elements ist solid mit 2 px. Enter führt zu /tickets/100201. Danach „Zurück“ und Warten auf .week |
- Step 4: Gate
p3ingates.mjsanlegen
p3 ist p2 plus die Ansichten dieses Pakets im bereichsbezogenen Gate icon-svg (Schema der Übersicht). step-failed, external-request und console-error sind ohnehin aktiv, ignore von p2 bleibt erhalten:
GATES.p3 = { ...GATES.p2, scoped: { ...GATES.p2.scoped, 'icon-svg': { ...GATES.p2.scoped['icon-svg'], views: [...GATES.p2.scoped['icon-svg'].views, 'planboard', 'planboard-*'] } } }
- Step 5: Vorher-Lauf (muss scheitern)
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p3-vorher && "$W/shots.sh" p3-vorher "$W/shots/p3-vorher" --only planboard --gate p3
Expected: Exit 1, icon-svg findet die zwei Chevron-Icons (dazu step-failed, weil die neue Kopfzeile fehlt). Die Bilder shots/p3-vorher/1440/planboard.png und 390/planboard.png sind die Vorher-Bilder mit den neuen Testdaten. Die anderen fünf Ansichten brechen vor dem Umbau an den fehlenden Knöpfen und werden hier nicht gestartet.
Task 5: Planboard-CSS und Seite
Files:
- Modify:
src/styles/global.css(neuer Block „Planboard (P3)“ unmittelbar vor/* ===== KOMPAT (bis P10) ===== */) - Modify:
src/pages/PlanboardPage.jsx(Zeilen 1–119 ersetzen)
Interfaces:
-
Consumes:
PageHeader({ title, subtitle, actions }),Button({ variant })aus P1.ticketPath(ref)undticketRef(ticket)aus P2.planboardView.jsaus Task 3.groupAppointmentsByDay,countInRange,dayKeyauslib/planboard.js.- Tokens
--bg-hover,--bg-subtle,--appt-hover,--appt-akquise,--appt-akquise-hover,--accent.
-
Produces: die Klassen
.week,.day(.today,.weekend),.day-head(.wd,.dn),.appt(.akquise,.tm,.tt,.cu),.none. Spätere Pakete benutzen sie nicht. -
Step 1: CSS-Block anlegen
Alle Werte aus quelle/styles.css, Zeilen 379–397, mit den Tokens statt der Literale #eef1f4, #fdf4e3 und #fbeccd. Zusätzlich gelten diese Ergänzungen:
| Selektor | Wert |
|---|---|
.day-head |
zusätzlich flex-wrap: wrap; row-gap: 0 |
.appt |
zusätzlich overflow-wrap: anywhere |
.day.weekend .appt:not(.akquise) |
background: var(--bg) |
.day.weekend .appt:not(.akquise):hover |
background: var(--appt-hover) |
@media (max-width: 860px) |
am Ende des Blocks: .week { grid-template-columns: 1fr; border-top: 0; min-height: 0 }, .day { border-left: 0; padding: 12px 0; border-top: 1px solid var(--line) }, .day.weekend { background: none }, .day.weekend .appt:not(.akquise) { background: var(--bg-hover) } (Quelle: Zeilen 535–537) |
Der Block beginnt mit /* ===== Planboard (P3) ===== */.
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && sed -n '/Planboard (P3)/,/KOMPAT (bis P10)/p' src/styles/global.css | rg -n "!important|#[0-9a-fA-F]{3,8}\b"
Expected: nur ein Treffer, #fff an .day.today .dn. Gab es in Task 1 Step 4 Mobil-Regeln aus P1 für .week oder .day, sind sie jetzt aus dem P1-Block entfernt, sodass sie nur noch hier stehen (rg -n "^\s*\.(week|day)\b" src/styles/global.css zeigt nur Zeilen im P3-Block).
- Step 2:
PlanboardPage.jsxneu schreiben
Der Code der Seite (State, useWorkorders({ limit: 500 }), useEmployees, byDay, weekCount, navigateWeek, goToday) bleibt. Das Markup wird:
| Element | Umsetzung |
|---|---|
| Wurzel | <div className="page"> |
| Kopf | <PageHeader title="Planboard" subtitle={planboardSubtitle({ weekStart: currentWeek, count: weekCount, loading })} actions={…} /> |
| Aktionen | drei Knöpfe in dieser Reihenfolge: <Button variant="quiet" aria-label="Vorherige Woche">←</Button>, <Button>Heute</Button>, <Button variant="quiet" aria-label="Nächste Woche">→</Button>. Kein schwarzer Knopf im Kopf |
| Woche | <div className="week"> mit sieben <div className="day">. Klassen today bei isSameDay(day, new Date()) und weekend ab dem sechsten Tag. Attribute role="group", aria-label={labels.full}, aria-current="date" nur bei heute |
| Tageskopf | <div className="day-head"><span className="wd">{labels.weekday}</span><span className="dn">{labels.number}</span></div> |
| Leerer Tag | <div className="none">Keine Termine</div> |
| Termin | <Link className={appointmentClass(entry.type)} to={ticketPath(ticketRef({ woid: entry.woid, $id: entry.id }))} title={appointmentTitle(entry)}> mit .tm (appointmentTimeLabel), .tt (entry.customerName || entry.topic), einem .cu mit entry.topic (nur bei vorhandenem Kundennamen) und einem .cu mit appointmentOwnerLabel(entry) |
Es entfallen: alle Icon-Importe, Card, useNavigate, TYPE_COLORS, format für die Kopfzeile und jedes style.
- Step 3: Hygiene prüfen
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|style=\{\{|navigate\(|Card|TYPE_COLORS" src/pages/PlanboardPage.jsx
Expected: keine Treffer.
- Step 4: Bauen und Gate
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p3 && "$W/shots.sh" p3 "$W/shots/p3" --gate p3
Expected:
-
Exit 0, alle Zusicherungen aus Task 4 Step 3 erfüllt,
icon-svgist für dieplanboard*-Ansichten sauber. -
In
shots/p3/390x844/planboard.pngstehen die Tage untereinander, der Mittwoch trägt die schwarze Zahl, und es gibt keine Spaltenlinien. -
In
shots/p3/1440x900/planboard.pngsind Samstag und Sonntag hell abgesetzt, die Donnerstagsspalte ist länger, und die Akquise-Termine sind hell orange. -
Step 5: Commit
git add src/pages/PlanboardPage.jsx src/styles/global.css
git commit -m "Planboard: Wochenansicht ohne Karten (Tagesspalten, heute als Pille, Termine als Links)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
Task 6: Gate P3, Review, Tag
- Step 1: Rebase
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 = N0 + 9. Liegen neue Commits auf test, kann die Zahl höher sein; sie wird notiert.
- Step 2: Vollständiger Gate-Lauf
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p3 && "$W/shots.sh" p3 "$W/shots/p3" --gate p3
Expected:
-
Gate
p3Exit 0. -
Keine Verstöße gegen
dark-surface,low-contrast,overflow-x,console-error,external-requestundstep-failed, auch auf allen übrigen Ansichten. -
Die Planboard-Ansichten haben kein
icon-svg. -
Step 3: Bundle-Größe
Die Größen stehen in der Ausgabe von check.sh (Step 1). Sie werden mit dem Stand von design-p2 verglichen. Das JS wird kleiner (zwei Icon-Importe weniger), das CSS wächst um rund 1 KB.
- Step 4: Code-Review (sonnet)
Geprüft wird der Diff design-p2..HEAD gegen die Spec-Abschnitte „Gestaltungsregeln“, „Bausteine“ und Paket P3. Schwerpunkte:
- keine Icons, keine
style={{, kein Farbliteral in der Seite ticketPath(ticketRef(…))ist das einzige Linkziel- das
aria-labelder Spalten und der Pfeilknöpfe - der CSS-Block benutzt nur Tokens, kein
!important lib/planboard.jsist unverändert (git diff design-p2 -- src/lib/planboard.jsist leer)
Wichtige Befunde werden behoben, Kleinigkeiten notiert. Danach kommt der Rebase aus Step 1 noch einmal und Step 2 als Kurzlauf mit --only planboard,planboard-fehler.
- Step 5: Tag und Übergabe
Run: source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" && git tag design-p3 && git log --oneline origin/test..design-p3 && git rev-parse design-p3
Der Eigentümer bekommt:
-
die Screenshots aus
shots/p3/1440x900undshots/p3/390x844(Auswahl:planboard,planboard-woche-vor,planboard-fehler) und den Vorher-Vergleich ausshots/p3-vorher -
den Hinweis, dass der Tageskopf die heutigen Texte behält (der Prototyp kürzt auf „Mo 5.“) und der Prototyp-Fußtext samt Legende fehlt
-
die Push-Zeile
git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p3^{commit}:refs/heads/test' -
die Rücknahme:
git revert --no-commit design-p2..design-p3, Commit und Push durch den Eigentümer (gilt nur, solange P3 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'entsprichtdist/index.htmldes Tags. -
Ein lesender Sichttest im Chrome des Eigentümers: Planboard öffnen, Woche vor und zurück, ein Termin öffnet das Ticket.