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

750 lines
35 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WOMS „Weißes Design“ P1: Fundament – 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 P1 ist das ganze Ticketsystem weiß und lesbar:
- Das Menü ist eine Textliste.
- Das Login ist neu.
- Alle Dialoge sind weiß.
- Die neuen Bausteine stehen für P2 bis P9 bereit.
Die Innenlayouts der Seiten bleiben vorerst alt.
**Architecture:** `src/styles/global.css` bekommt fünf Blöcke:
1. Tokens und Aliase
2. Basis (Reset, Hilfsklassen, Links)
3. Bausteine (Prototyp-Klassen)
4. KOMPAT (Altklassen im neuen Look, bis P10)
5. Bereichs-CSS (Finanzen, Rechtliches), bis auf Pflicht-Fixes unverändert
Gleichnamige Altdefinitionen werden vor dem Einfügen der Bausteine gelöscht. Die Komponenten unter `src/components/ui/` behalten ihre Schnittstellen. Neu kommen `Dialog`, `Stat`, `Logo` und `statusTone` hinzu.
**Tech Stack:** React 18, react-router-dom 6, Vite 5, reines CSS. Tests mit `node --test`, Screenshots mit dem Werkzeug aus P0.
**Spec:** `docs/superpowers/specs/2026-10-04-weisses-design-design.md`, Abschnitte „Gestaltungsregeln“, „Design-Tokens“, „Bausteine“, „Übergangsschicht KOMPAT“ und Paket P1. Die Prototyp-Werte stehen in `/root/reports/ticketsystem-design-2026-10-04/quelle/styles.css`. Globale Regeln, Befehlsregeln (`env.sh`, `check.sh`) und gemeinsame Schnittstellen stehen in `2026-10-05-weisses-design-00-uebersicht.md`.
## Global Constraints
Es gelten die Regeln der Übersicht. Für P1 kommt hinzu:
- Jeder Run-Block beginnt mit `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh`. Danach gelten `$WT`, `$W` und `$TMPDIR`.
- Werte für Tokens, Abstände, Größen und Schatten werden aus der Spec bzw. aus `quelle/styles.css` übernommen und nicht neu erfunden.
- Nicht übernommen werden `html[data-accent]`, `.proto*` und `.nav-gap`.
- Verhalten, Routen und Auth bleiben unverändert. Ausnahmen:
- Der Footer entfällt.
- Das Einklappen entfällt.
- Die Schublade unter 860 px kommt neu dazu.
- Seiten in `src/pages/*` werden nur dort angefasst, wo dieser Plan es nennt:
- `LoginPage`
- `ProjectsPage`: Wurzelklasse
- `AdminPage`: Wurzelelement
- `ServerpulsPage`: Diagrammfarben
- Die Spec erlaubt keine neue `robots`-Meta-Zeile. `index.html` bekommt nur das Favicon.
## Review Focus
- **Übersichtspunkt 2 (lange Inhalte am Handy):** `overflow-x` ist im Gate `p1` für alle Ansichten scharf.
- **Übersichtspunkt 4 (Tastatur):** Die Esc- und Sperrlogik des Dialogs prüft ein Lib-Test in Task 7.
- **Altseiten:** Jede Altseite bleibt mit KOMPAT lesbar. Das prüft das Gate `p1` mit `dark-surface` und `low-contrast`, auch für Verläufe.
- **Alt-Dialoge:** Die zehn Alt-Dialoge müssen bei 390×700 und 1440×700 bis zum Speichern-Knopf erreichbar sein. Das prüft `submitReachable` in jeder `dlg-*`-Ansicht.
---
### Task 0: Paketstart
- [ ] **Step 1: Stand prüfen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git fetch origin && git merge-base --is-ancestor design-p0 HEAD && git log --oneline origin/test..feature/terminplaner-tp2 -- src/styles src/components/ui src/App.jsx src/components/Navbar.jsx src/pages/LoginPage.jsx src/lib/navLinks.js src/lib/ticketStatus.js && git rebase origin/test && "$W/check.sh"`
Expected:
- Exit 0.
- Die TP2-Zeile gibt nichts aus. Gibt sie etwas aus: stoppen und den Eigentümer fragen. Empfehlung: TP2 zuerst nach `test` bringen.
- [ ] **Step 2: Gate `p1` um die Menü-Prüfung ergänzen**
In `$W/gates.mjs` bekommt `p1` den Eintrag `scoped: { 'icon-svg': { views: '*', ignore: ['#inhalt *'] } }`. Damit prüft `icon-svg` ab P1 Menü, Mobil-Leiste und Login auf allen Ansichten, den Seiteninhalt aber erst ab dem jeweiligen Paket.
### Task 1: `statusTone`
**Files:**
- Modify: `src/lib/ticketStatus.js` (neuer Export nach `statusGroup`)
- Test: `tests/unit/ticketStatus.test.mjs` (neu)
**Interfaces:**
- Consumes: `statusGroup(status)` aus derselben Datei. Es liefert die Gruppen-ID oder `null`.
- Produces: `statusTone(status) → '' | 'blue' | 'orange' | 'violet' | 'green' | 'plain'`.
- [ ] **Step 1: Failing Test**
```js
const { statusTone } = await loadModule('src/lib/ticketStatus.js')
test('statusTone: Ton je gespeichertem Wert', () => {
for (const s of ['Open', '', undefined, null, 'Completed']) assert.equal(statusTone(s), '')
for (const s of ['Occupied', 'Assigned', 'Added Info', 'In Kontakt']) assert.equal(statusTone(s), 'blue')
for (const s of ['Awaiting', 'Halted']) assert.equal(statusTone(s), 'orange')
assert.equal(statusTone('In Test'), 'violet')
for (const s of ['Closed', 'Zugesagt']) assert.equal(statusTone(s), 'green')
for (const s of ['Cancelled', 'Aborted', 'Abgesagt']) assert.equal(statusTone(s), 'plain')
})
```
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/ticketStatus.test.mjs`
Expected: FAIL, `statusTone is not a function`.
- [ ] **Step 2: Implementieren**
Zuordnung über `statusGroup`:
| Gruppe | Ton |
|---|---|
| `open` | `''` |
| `working` | `'blue'` |
| `waiting` | `'orange'` |
| `review` | `'violet'` |
| `done` | `'green'` |
| `cancelled` | `'plain'` |
| `null` | `''` |
`statusClass` bleibt bis P2.
- [ ] **Step 3: Grün**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/ticketStatus.test.mjs && "$W/check.sh" --nur-tests`
Expected: PASS, Exit 0.
- [ ] **Step 4: Commit**
```bash
git add src/lib/ticketStatus.js tests/unit/ticketStatus.test.mjs
git commit -m "Ticketstatus: statusTone fuer die neuen Badge-Toene" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 2: Tokens, Aliase, Basis, Schrift
**Files:**
- Modify: `src/styles/global.css`
- Zeilen 1-10 (`@font-face`) löschen
- `:root`, alte Zeilen 18-71, ersetzen
- `body`/Scrollbar/`::selection`, alte Zeilen 73-106, ersetzen
- alte Hilfsregeln `.muted`/`.faint` (um Zeile 1051) ersetzen
- Modify: `src/pages/AdminPage.css` (Zeilen 2-3, `--admin-line*`, löschen)
- Delete: `src/assets/fonts/lato-regular.ttf`, `src/assets/fonts/OFL-Lato.txt`
- Modify (Werkzeug): `$W/views.mjs` bekommt die Ansicht `basis-weiss`
**Interfaces:**
- Produces: alle Tokens und Aliase der Spec, dazu die Basis- und Hilfsklassen aus der Übersicht.
- [ ] **Step 1: Prüf-Ansicht (failing)**
`basis-weiss` öffnet `/tickets`. Der Schritt prüft, dass `getComputedStyle(document.body).backgroundColor === 'rgb(255, 255, 255)'` und dass `font-family` mit `-apple-system` beginnt.
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1-t2" --only basis-weiss --gate p0`
Expected: Exit 1 mit `step-failed`.
- [ ] **Step 2: `:root` ersetzen**
- Den Token-Block der Spec wörtlich übernehmen.
- Darunter folgt die Alias-Tabelle der Spec. Sie enthält `--admin-line` und `--admin-line-strong`.
- Die beiden Zeilen in `AdminPage.css` entfallen.
- [ ] **Step 3: Basis setzen**
Aus `quelle/styles.css` Zeilen 63-91 übernehmen. Zusätzlich gilt:
| Regel | Wert |
|---|---|
| `html` | `color-scheme: light` |
| `body` | `font-family: var(--font); font-size: 14px; line-height: 20px; color: var(--text); background: var(--bg); -webkit-font-smoothing: antialiased`, ohne `overflow: hidden` |
| Formularelemente | `button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer }` und `input, select, textarea { font: inherit; color: inherit }` |
| `a` | `color: inherit; text-decoration: none` |
| `a:not([class])` | Unterstrich in `--line-strong`, `text-underline-offset: 3px`, beim Überfahren `text-decoration-color: currentColor` |
| Hilfsklassen | `.num` (`tabular-nums`), `.muted` (`--text-2`), `.faint` (`--text-3`), `.small` 13px, `.xs` 12/16, `.strong` 600, `.nowrap` |
| `.link`, `.link:hover`, `.link-danger`, `.link-danger:hover` | Werte aus dem Prototyp |
| `:focus-visible` | `outline: 2px solid #111111; outline-offset: 2px` |
| `:disabled` | `opacity: .55; cursor: not-allowed` |
| `[readonly]` | `background: var(--bg-subtle)` |
- [ ] **Step 4: Schrift entfernen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git rm -q src/assets/fonts/lato-regular.ttf src/assets/fonts/OFL-Lato.txt && rg -n "lato|Lato" src`
Expected: keine Treffer.
- [ ] **Step 5: Grün**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1-t2" --only basis-weiss --gate p0`
Expected: Exit 0.
- [ ] **Step 6: Commit**
```bash
git add src/styles/global.css src/pages/AdminPage.css
git commit -m "Design: weisse Tokens, Aliase, Basis und Hilfsklassen, Systemschrift" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 3: Bausteine-CSS
**Files:**
- Modify: `src/styles/global.css`: Altdefinitionen gleichnamiger Klassen löschen, Block „Bausteine“ hinter der Basis anlegen
**Interfaces:**
- Produces: alle Klassen aus der Übersicht („CSS-Klassen in `global.css`“).
- [ ] **Step 1: Gleichnamige Altdefinitionen löschen**
Gelöscht werden die Regeln dieser Selektoren samt ihrer Varianten (Hover, Kinder, Media-Queries). Die Zeilenangaben beziehen sich auf den Stand 603d08c, gesucht wird nach dem Selektor:
| Selektor | Zeilen (603d08c) |
|---|---|
| `.btn` und `.btn:hover`, ohne `.btn-dark`/`.btn-teal`/`.btn-green` (die gehen nach KOMPAT bzw. sterben in Task 8) | ab 188 |
| `.table`, `.table th`, `.table td`, `.table td[rowspan]` | 236-275 |
| `.overlay` und `.overlay-close` (Neufassung in KOMPAT, Task 4) | ab 563 |
| `.page` | 867 |
| `.badge` (Basis) | 909-915 |
| `.urgent-toggle` | 971-976 |
| `.list-row` | 997-1005 |
| `.toolbar` | 1018 |
| `.seg` | 1019-1024 |
| `.empty` | 1028 |
| `.field` | 1032 |
Der übrige alte Block „REDESIGN“ (796-1049) bleibt bis Task 4. Dazu gehören `.ui-card*`, `.ui-btn*`, `.badge-*`, `.kpi*`, `.tcard*`, `.ui-table`, `.list`, `.ticket-tabs`/`.ticket-tab*` und die Hilfsregeln `.mt-*`, `.flex*`, `.gap-*`, `.grow`, `.wrap` und `.text-ellipsis`.
- [ ] **Step 2: Bausteine übernehmen**
Aus `quelle/styles.css` werden diese Abschnitte übernommen:
- „Grundgerüst“ ohne `.nav-gap`
- „Seitenkopf“
- „Knöpfe“
- „Status-Marken“
- „Reiter“
- „Filterzeile“
- „Tabellen“
- „Kennzahlen / Schlüsselwerte“
- „Ticket-Kopf“
- „Listen“
- „Dialog“
- „Anmeldung“
Dazu kommen die Media-Queries 1100 und 860. **Ausgeschlossen** sind dort `.proto*`, `.ws*`, `.week`, `.day*` und `.appt` (sie kommen mit P2 und P3) sowie `html[data-accent]`.
Zusätzlich:
| Regel | Wert |
|---|---|
| `.page section + section` | `margin-top: 36px` |
| `.nav-dot` | `background: var(--nav-dot)` |
| `.nav-dot.critical` | `background: var(--danger)` |
| `.dialog-wide` | `max-width: 760px` |
| `.msg` | 13px |
| `.msg.err` | Farbe `--red-fg` |
| `.msg.ok` | Farbe `--green-fg` |
| `.msg.warn` | Farbe `--orange-fg` |
| `.loading` | `padding: 56px 0; text-align: center; color: var(--text-2)` |
| `.v.neg` | Farbe `--red-fg` |
| `.stat .sub` | `font-size: 12px; line-height: 16px; color: var(--text-2)` |
| `.badge.plain` | Farbe `--text-2` |
| `.tab.active`, `.tab[aria-selected="true"]` | aktiver Reiter |
| `.seg button.on`, `.seg button[aria-pressed="true"]` | aktiver Teil |
| `.autocomplete-list` | `background: var(--bg); box-shadow: var(--shadow-pop); border-radius: 8px` |
| `.autocomplete-item.active`, `.autocomplete-item:hover` | `background: var(--bg-hover)` |
| `.datetime-pair` | zwei Felder nebeneinander, Abstand 8px, kein `color-scheme: dark` |
| `[aria-invalid="true"]` und `.is-invalid` an `.input`, `.select`, `.textarea`, `.form-control` | `box-shadow: 0 0 0 1px var(--danger), 0 0 0 4px var(--danger-ring)` |
- [ ] **Step 3: Jede Basisregel genau einmal**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && for s in btn table overlay page badge urgent-toggle list-row toolbar seg empty field; do printf '%s ' "$s"; rg -c "^\.$s( |\{|,)" src/styles/global.css; done`
Expected: Hinter jedem Namen steht `1`.
- [ ] **Step 4: Bauen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" --nur-build`
Expected: Exit 0.
- [ ] **Step 5: Commit**
```bash
git add src/styles/global.css
git commit -m "Design: Bausteine aus dem Prototyp, gleichnamige Altregeln ersetzt" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 4: KOMPAT und tote Klassen
**Files:**
- Create: `$W/css-klassen.mjs`
- Create: `docs/superpowers/plans/2026-10-05-weisses-design-p1-tote-klassen.md`
- Modify: `src/styles/global.css` (Block „KOMPAT“, Legacy löschen)
**Interfaces:**
- Produces:
- Der KOMPAT-Block nach der Spec-Tabelle „Übergangsschicht KOMPAT“. P10 entfernt ihn.
- `css-klassen.mjs <worktree> [--ohne-ausnahmen]` gibt JSON aus: `{ defined, used, dead, undefinedUsed }`.
- [ ] **Step 1: Skript schreiben**
„Benutzt“ ist eine Klasse, wenn eine dieser Bedingungen zutrifft:
- Sie steht als Wort in einem `className`-Literal oder Template-String in `src/**/*.jsx`.
- Sie steht in einer Zeichenkette in `src/lib/*.js` oder `src/hooks/*.js`.
- Ein Template-Präfix deckt sie ab: `xxx-${…}` gilt für `xxx-*`.
- Sie steht in der Ausnahmeliste: `fin-neg`, `fin-fixed-off`, `legal-tone-*`, `legal-version-*`, `badge-*`, `status-*`, `ticket-tab-active`, `ui-btn-*`, `side-dot-*`, `woms-chat`, `chat-*`. Mit `--ohne-ausnahmen` entfällt diese Liste.
„Definiert“ ist eine Klasse, wenn sie in einem Selektor in `global.css`, `chat.css`, `serverpuls.css`, `admin-areas.css` oder `pages/AdminPage.css` steht.
- [ ] **Step 2: Liste erzeugen und gegenprüfen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node "$W/css-klassen.mjs" "$WT" > "$TMPDIR/klassen.json"`
Danach für jeden Kandidaten in `dead`: `rg -w <name> src --glob '!*.css'`. Ein Kandidat gilt nur bei 0 Treffern als tot. Die bestätigte Liste kommt mit Klassenname und definierender Datei:Zeile in `2026-10-05-weisses-design-p1-tote-klassen.md`.
Expected: etwa 66 Namen. Liegt die Zahl unter 40 oder über 90, wird die Abweichung dort begründet.
- [ ] **Step 3: Legacy löschen, KOMPAT anlegen**
Gelöscht werden:
- die Regeln der bestätigten toten Klassen
- alle verbliebenen `!important`
- `.side*` außer `.side-count` und `.side-logo` (die gehen nach KOMPAT)
- `.footer`, `.navbar*`, `.ticket-row*`, `.worksheet-expansion*`, `.slider`, `.drop-zone`, `.modern-card`
- die Legacy-Responsive-Regeln (alt 789-794)
- die Keyframes `slideDown`, `fadeIn` und `slideIn`
- `.kpi*`, weil `Kpi` ab Task 6 `Stat` rendert
Danach wird KOMPAT laut Spec-Tabelle angelegt, markiert mit `/* ===== KOMPAT (bis P10) ===== */`. Der Block enthält auch `.page .page` und `.page .main-content`, die Abstand und Breite neutralisieren.
- [ ] **Step 4: Keine benutzte Klasse ohne Regel**
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 diese Namen tauchen auf:
- `p-4`, `btn-sm`, `status-filter`, `supporters-editor`, `timeline`
- die Bootstrap-Namen aus `WorksheetList.jsx` (heute schon wirkungslos)
- [ ] **Step 5: Altseiten ansehen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1-t4" --gate p1; echo "exit $?"`
Expected: `step-failed` (`submitReachable`) ist auf keiner `dlg-*`-Ansicht mehr gemeldet. Erlaubt sind nur noch Treffer `dark-surface` und `low-contrast` in Dateien aus Task 9.
- [ ] **Step 6: Commit**
```bash
git add src/styles/global.css docs/superpowers/plans/2026-10-05-weisses-design-p1-tote-klassen.md
git commit -m "Design: KOMPAT-Schicht fuer Altklassen, tote Regeln und !important entfernt" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 5: Gerüst, Menü, Logo, Favicon
**Files:**
- Create: `src/components/Logo.jsx`, `public/favicon.svg` (Kopie von `quelle/favicon.svg`)
- Modify:
- `src/App.jsx`
- `src/components/Navbar.jsx`
- `src/lib/navLinks.js` (`hot: true` am Chat-Unterpunkt)
- `index.html` (nur `<link rel="icon" href="/favicon.svg" type="image/svg+xml">`)
- `src/styles/serverpuls.css` (Zeilen 70-74 `.side-dot*` löschen)
- `src/pages/ProjectsPage.jsx` (Wurzel `main-content` → `page`)
- `src/pages/AdminPage.jsx` (`<main className="page admin-page">` → `<div className="page admin-page">`, zwei Stellen)
- Delete: `src/components/Footer.jsx`, `src/lib/sidebar.js`, `tests/unit/sidebar.test.mjs`
- Test: `tests/unit/navLinks.test.mjs`
- Modify (Werkzeug): `$W/views.mjs` bekommt die Ansicht `menue-mobil`
**Interfaces:**
- Consumes: Klassen aus Task 3.
- Produces:
- `Logo({ size = 24 })` mit `aria-hidden="true"`. Die Pfade kommen aus `quelle/app.js`, Konstante `LOGO`.
- `Sidebar({ onNavigate })`.
- [ ] **Step 1: Test für `hot` (failing)**
In `tests/unit/navLinks.test.mjs` die Zeile `assert.equal(kunden.children.find((c) => c.key === 'chat').hot, true)` ergänzen. Die bestehenden `deepEqual` bleiben.
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/navLinks.test.mjs`
Expected: FAIL.
- [ ] **Step 2: `navLinks.js` ergänzen, Test grün**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/navLinks.test.mjs`
Expected: PASS.
- [ ] **Step 3: Logo und Favicon anlegen**
- [ ] **Step 4: Gerüst in `App.jsx`**
`AppContent` hält `menuOpen` und baut dieses Gerüst:
```jsx
<div className={`shell${menuOpen ? ' menu-open' : ''}`}>
<Sidebar onNavigate={close} />
<div className="scrim" onClick={close} />
<div className="main">
<div className="mobile-bar">
<Link className="brand" to="/tickets"><Logo />WEBklar</Link>
<button type="button" className="btn btn-quiet" onClick={open} aria-expanded={menuOpen} aria-controls="menue">Menü</button>
</div>
<main id="inhalt"><AppRoutes /></main>
</div>
</div>
```
- Ein Effekt auf `location.pathname` (nur darauf) schließt die Schublade und ruft `window.scrollTo(0, 0)` auf.
- Ein Esc-Listener ist nur bei `menuOpen` aktiv.
- Der Ladezustand von `ProtectedRoute` ist `<div className="loading">Wird geladen …</div>`.
- [ ] **Step 5: `Navbar.jsx` als Textliste**
Aufbau: `<aside className="sidebar" id="menue">`, darin `.brand` (Logo und „WEBklar“), `<nav className="nav">` mit `.nav-item` und `.sub` und am Ende `.sidebar-foot`.
| Element | Markup |
|---|---|
| Pausiert | `<span className="nav-item paused" aria-disabled="true">Leads<span className="nav-count">pausiert</span></span>` |
| Zähler | `<span className={`nav-count${child.hot ? ' hot' : ''}`}>` |
| Serverpuls | `<span className={`nav-dot${dot === 'critical' ? ' critical' : ''}`} data-testid="sp-nav-dot" title=…>`, nur wenn `dot` nicht `'ok'` ist. Titeltexte wie heute. |
| Fuß | `<span className="who text-ellipsis">{user.name \|\| user.email}</span>` und `<button type="button" className="link" onClick={handleLogout}>Abmelden</button>` |
Klicks auf Einträge rufen `onNavigate()` auf.
Entfernt werden:
- `@tabler`-Imports und `ICONS`
- Einklappen und `startsCollapsed`
Bleiben:
- `isActive` und `isLinkActive`
- das Serverpuls-Polling
- `useSidebarCounts`
- [ ] **Step 6: Altdateien löschen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git rm -q src/components/Footer.jsx src/lib/sidebar.js tests/unit/sidebar.test.mjs && rg -n "Footer|startsCollapsed|lib/sidebar" src tests`
Expected: keine Treffer.
- [ ] **Step 7: Ansicht `menue-mobil`**
Bei 390×844 läuft:
1. Klick auf „Menü“: `.shell.menu-open` ist da.
2. Screenshot.
3. Esc: `.menu-open` ist weg.
4. Menü erneut öffnen, Klick auf „Projekte“: Der Pfad ist `/projects`, und das Menü ist zu.
- [ ] **Step 8: Prüfen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1-t5" --only tickets,kunden,serverpuls,menue-mobil --gate p1`
Expected:
- Exit 0.
- Im Menü ist kein `svg` außer dem Logo.
- [ ] **Step 9: Commit**
```bash
git add src/App.jsx src/components/Navbar.jsx src/components/Logo.jsx src/lib/navLinks.js tests/unit/navLinks.test.mjs index.html public/favicon.svg src/styles/serverpuls.css src/pages/ProjectsPage.jsx src/pages/AdminPage.jsx
git commit -m "Design: Geruest mit Textmenue, Schublade fuers Handy, Logo; Footer und Einklappen entfallen" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 6: UI-Bausteine
**Files:**
- Create: `src/components/ui/tones.js`, `src/components/ui/Stat.jsx`
- Modify:
- `Button.jsx`, `Badge.jsx`, `Card.jsx`, `PageHeader.jsx`, `Tabs.jsx`, `EmptyState.jsx`, `Field.jsx`, `Kpi.jsx`, `StatusPill.jsx`, `index.js`
- nur Klasse `form-control` → `input` bzw. `select`: `AutocompleteInput.jsx`, `DateTimeInput.jsx`, `DurationSelect.jsx`
- Test: `tests/unit/uiTones.test.mjs`
**Interfaces:**
- Produces:
- die Signaturen aus der Übersicht
- `badgeToneClass(tone) → string`
- `BUTTON_CLASSES`
Beide liegen in `tones.js`. `Button` setzt keinen eigenen Standard-`type`.
- [ ] **Step 1: Failing Test**
```js
const { badgeToneClass, BUTTON_CLASSES } = await loadModule('src/components/ui/tones.js')
test('badgeToneClass', () => {
for (const t of ['muted', 'grey', undefined, 'unbekannt']) assert.equal(badgeToneClass(t), 'badge')
assert.equal(badgeToneClass('info'), 'badge blue')
assert.equal(badgeToneClass('blue'), 'badge blue')
assert.equal(badgeToneClass('ok'), 'badge green')
assert.equal(badgeToneClass('warn'), 'badge orange')
assert.equal(badgeToneClass('danger'), 'badge red')
assert.equal(badgeToneClass('violet'), 'badge violet')
assert.equal(badgeToneClass('plain'), 'badge plain')
})
test('Button-Varianten', () => {
assert.deepEqual(BUTTON_CLASSES, { default: 'btn', ghost: 'btn', primary: 'btn btn-primary', quiet: 'btn btn-quiet', danger: 'link-danger' })
})
```
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/uiTones.test.mjs`
Expected: FAIL.
- [ ] **Step 2: `tones.js` anlegen, Test grün**
- [ ] **Step 3: Komponenten umschreiben**
| Komponente | Änderung |
|---|---|
| `Card` | ohne Titel und ohne Aktionen nur `<section>` |
| `PageHeader` | Brotkrumen als `Link`, wenn `to` gesetzt ist. Das letzte Element ist Text, das „/“ steht in `<span className="faint">`. |
| `Tabs` | `active` und `aria-selected` |
| `EmptyState` | ohne Inline-Styles |
| `Field` | Hinweis als `.hint` |
| `Kpi` | gibt `Stat` zurück |
| `StatusPill` | ohne `pillBase` |
- [ ] **Step 4: Prüfen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1-t6" --only tickets,ticket,kunde-overview,finanzen --gate p1`
Expected: Exit 0. Die Status-Badges in `tickets` zeigen die neuen Töne.
- [ ] **Step 5: Commit**
```bash
git add src/components/ui tests/unit/uiTones.test.mjs
git commit -m "Design: UI-Bausteine rendern die neuen Klassen (Button, Badge, Card, Kopf, Reiter, Stat)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 7: `Dialog`
**Files:**
- Create: `src/components/ui/Dialog.jsx`, `src/lib/dialogKeys.js`
- Modify: `src/components/ui/index.js`, `$W/views.mjs` (Ansicht `dlg-probe`)
- Test: `tests/unit/dialogKeys.test.mjs`
**Interfaces:**
- Consumes: `useDialogFocus(dialogRef)` aus `src/hooks/useDialogFocus.js`, unverändert.
- Produces:
- `Dialog` laut Übersicht
- `shouldCloseOnKey({ key, defaultPrevented }, busy) → boolean`
- [ ] **Step 1: Failing Test**
```js
const { shouldCloseOnKey } = await loadModule('src/lib/dialogKeys.js')
test('Esc schliesst nur ohne Sperre und ohne vorherige Behandlung', () => {
assert.equal(shouldCloseOnKey({ key: 'Escape', defaultPrevented: false }, false), true)
assert.equal(shouldCloseOnKey({ key: 'Escape', defaultPrevented: true }, false), false)
assert.equal(shouldCloseOnKey({ key: 'Escape', defaultPrevented: false }, true), false)
assert.equal(shouldCloseOnKey({ key: 'Enter', defaultPrevented: false }, false), false)
})
```
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/dialogKeys.test.mjs`
Expected: FAIL.
- [ ] **Step 2: Implementieren, Test grün**
- [ ] **Step 3: `Dialog.jsx`**
| Teil | Umsetzung |
|---|---|
| Esc | `keydown`-Listener auf `document`, prüft mit `shouldCloseOnKey(e, busy)` und ruft dann `onClose()` auf |
| Hintergrund | Klick ohne Wirkung |
| Box | `role="dialog"`, `aria-modal="true"`, `aria-labelledby={titleId}` (`useId()`), `ref`, `tabIndex={-1}` |
| Overlay | `onKeyDown` aus `useDialogFocus` |
| `×` | `<button type="button" className="dialog-close" aria-label="Schließen" disabled={busy} onClick={onClose}>×</button>` |
| `as="form"` | die Box ist `<form onSubmit>` ohne `noValidate` |
- [ ] **Step 4: `dlg-probe`**
Die Ansicht öffnet `/tickets` und fügt per `page.evaluate` einen statischen Klon des Dialog-Markups ein: ein Titel, drei Felder und ein Fuß mit Absende-Knopf. Geprüft wird nur das CSS:
- Bei 390 px liegt die Box unten.
- Das Overlay hat Fenstergröße.
- `submitReachable` gelingt bei 390×700.
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/shots.sh" p1 "$W/shots/p1-t7" --only dlg-probe --gate p1`
Expected: Exit 0.
- [ ] **Step 5: Commit**
```bash
git add src/components/ui/Dialog.jsx src/components/ui/index.js src/lib/dialogKeys.js tests/unit/dialogKeys.test.mjs
git commit -m "Design: Dialog-Baustein (Esc, Sperre waehrend Speichern, Fokus, Bottom-Sheet)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 8: Login
**Files:**
- Modify: `src/pages/LoginPage.jsx` (nur Markup)
- Delete: `src/components/LetterGlitch.jsx`
**Interfaces:**
- Consumes:
- `useAuth()` mit `login`, `mfaPending`, `sendCode`, `verifyCode` und `cancelMfa`, unverändert
- `Logo`
- die Klassen `.login*`, `.field`, `.input`, `.code-input`, `.btn-primary.btn-block` und `.msg`
- [ ] **Step 1: Markup ersetzen**
Grundaufbau: `.login > .login-box > <Logo size={40} />`.
**Schritt E-Mail und Passwort**
| Element | Inhalt |
|---|---|
| Überschrift | h1 „Anmelden“ |
| Unterzeile | `.sub` „Ticketsystem für das WEBklar-Team“ |
| Feld E-Mail | Label „E-Mail“, `type="email"`, `autoComplete="username"`, Platzhalter `deine@email.com` |
| Feld Passwort | Label „Passwort“, `type="password"`, `autoComplete="current-password"`, `minLength={8}` |
| Knopf | `.btn.btn-primary.btn-block` „Weiter“, beim Laden „Login läuft...“ |
| Fehler | `.msg.err` mit `role="alert"` |
**Schritt Code**
| Element | Inhalt |
|---|---|
| Überschrift | h1 „Code eingeben“ |
| Text | die beiden Bestandssätze wörtlich |
| Hinweis | `notice` als `.msg.ok` |
| Feld | `#mfa-code` mit `className="input code-input"`, Label „Code aus der E-Mail“ |
| Knopf | „Anmelden“ bzw. „Prüfe…“ |
| Links | `.login-links` mit zwei `button.link` (`type="button"`): „Code erneut senden“ und „Abbrechen“ |
Ohne `challengeId` erscheint stattdessen „Code senden“ bzw. „Sende…“ und darunter „Abbrechen“.
**Fuß:** `.login-foot` „Webklar Ticket-Plattform · © {Jahr}“.
**Bleibt unverändert:** alle Handler und Zustände.
- [ ] **Step 2: `LetterGlitch` löschen und prüfen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git rm -q src/components/LetterGlitch.jsx && ! rg -n "LetterGlitch" src && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1-t8" --only login,login-code,login-code-senden --gate p1`
Expected: Exit 0. Im Bild `login-code-senden` steht der Knopf „Code senden“.
- [ ] **Step 3: Commit**
```bash
git add src/pages/LoginPage.jsx
git commit -m "Design: neues Login (weiss, Logo, Code-Schritte), Glitch-Hintergrund entfernt" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 9: Pflicht-Fixes gegen Unlesbares
**Files:**
- Create: `src/components/serverpuls/chartColors.js`, `$W/farben.mjs`
- Modify:
- CSS: `src/styles/chat.css`, `src/pages/AdminPage.css`, `src/styles/admin-areas.css`, `src/styles/serverpuls.css`, `src/styles/global.css` (Bereich Finanzen und Rechtliches)
- Projekt-Bausteine: `src/components/AssignedProjectCard.jsx` (Zeilen 27, 82, 102), `src/components/PreviewBlock.jsx`, `src/components/PreviewLinkButton.jsx`, `src/components/GiteaLinkButton.jsx`, `src/components/AnalyseRow.jsx` (`COLORS`)
- Arbeitsblätter: `src/components/CreateWorksheetModal.jsx` (Zeilen 194, 262), `src/components/WorksheetTimesModal.jsx` (Zeile 143), `src/components/WorksheetList.jsx` (nur Farben, siehe Step 4)
- Serverpuls: `src/components/serverpuls/Chart.jsx`, `src/pages/ServerpulsPage.jsx` (Serienfarben)
- Doku: `docs/superpowers/plans/2026-10-05-weisses-design-p1-tote-klassen.md` (Abschnitt „Bewusst offen“)
**Interfaces:**
- Produces:
- `CHART` laut Übersicht.
- `farben.mjs <worktree>` listet je Treffer Datei:Zeile, Literal und Grund:
- `kontrast<3` (Text auf Weiß)
- `dunkel-auf-akzent`
- `weiss` (`#fff`, `#ffffff`, `white`, `rgba(255,255,255,x)` als Text- oder Randfarbe außerhalb von `.btn-primary` und Elementen auf `--accent`)
- `verlauf` (`linear-gradient` in JSX oder CSS)
- [ ] **Step 1: Scan (failing)**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node "$W/farben.mjs" "$WT"`
Expected: eine Liste mit Treffern aller vier Gründe, unter anderem aus `WorksheetList.jsx`.
- [ ] **Step 2: Chat-Variablen**
Unter `.woms-chat` in `chat.css`:
- `--chat-accent: var(--accent)`
- `--chat-accent-hover: var(--accent-hover)`
- `--chat-accent-soft: var(--accent-soft)`
- `--chat-accent-line: rgba(17,17,17,.14)`
- `--chat-accent-text: var(--accent)`
- `--chat-on-accent: #ffffff`
- `--chat-surface: var(--bg)`
- `--chat-line: var(--line-strong)`
- `--chat-muted: var(--text-2)`
Weitere Regeln:
- Rand der eigenen Blase: `var(--line)`.
- `.woms-chat .chat-messages { background: var(--bg) }`.
- `.woms-chat .chat-composer-note`, `.woms-chat .chat-tray-item.is-error .chat-file-size` und `.woms-chat .chat-audio.is-error .chat-audio-time` bekommen `color: var(--red-fg)`.
- `.woms-chat .chat-composer-note.is-info` bekommt `color: var(--text-2)`.
- [ ] **Step 3: Literale umstellen nach fester Zuordnung**
| Literal | Token |
|---|---|
| `#a0aec0`, `#cbd5e0`, `#9aa7b4`, `#e2e8f0` (als Text) | `var(--text-2)` |
| `#34d399`, `#6ee7b7`, `#10b981` (als Text) | `var(--green-fg)` |
| `#f87171`, `#fca5a5`, `#fecaca`, `#ef4444` (als Text) | `var(--red-fg)` |
| `#fbbf24`, `#f59e0b` (als Text) | `var(--orange-fg)` |
| `#818cf8`, `#9d3dfe` | `var(--violet-fg)` |
| `#3b82f6`, `#60a5fa` | `var(--blue-fg)` |
| `rgba(255,255,255,x)` als Rand oder Linie | `var(--line)` |
| `rgba(255,255,255,x)` als Fläche | entfällt bzw. wird `var(--bg-subtle)` |
| `#05251c`, `#04130d` auf Akzent | `#ffffff` |
| `rgba(30,41,59,x)` und ähnliche dunkle Flächen | `var(--bg-subtle)` |
`.admin-primary-button` bekommt `color: #fff` und beim Überfahren `background: var(--accent-hover)`.
- [ ] **Step 4: `WorksheetList.jsx` minimal**
Nur die Farben werden geändert:
- Verläufe und Weiß-Alphas gehen auf Tokens nach der Tabelle oben.
- Weiße Schrift auf farbigen Köpfen wird `var(--text)` auf `var(--bg-subtle)`.
Struktur und Klassen bleiben. Den Umbau macht P2.
- [ ] **Step 5: Diagrammfarben**
`chartColors.js` wird angelegt. `Chart.jsx` nutzt `CHART.axis`, `CHART.grid` (nur horizontal), `CHART.tick` und als Standard-Serienfarbe `CHART.line`. `ServerpulsPage` setzt:
- alle Serien `CHART.line`
- Spitze `CHART.peak` mit `dash: [4, 4]`
- Füllung `CHART.fill`
- [ ] **Step 6: Scan erneut**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node "$W/farben.mjs" "$WT"`
Expected:
- Kein Treffer `kontrast<3`, `dunkel-auf-akzent` oder `weiss`.
- `verlauf`-Treffer nur dort, wo `.btn-primary` oder ein Canvas sie braucht.
- Jeder Rest steht unter „Bewusst offen“ im Dokument mit Zielpaket und gemessenem Kontrast von mindestens 3:1.
- [ ] **Step 7: Commit**
```bash
git add src/components/serverpuls/chartColors.js src/components/serverpuls/Chart.jsx src/pages/ServerpulsPage.jsx src/styles/chat.css src/pages/AdminPage.css src/styles/admin-areas.css src/styles/serverpuls.css src/styles/global.css src/components/AssignedProjectCard.jsx src/components/PreviewBlock.jsx src/components/PreviewLinkButton.jsx src/components/GiteaLinkButton.jsx src/components/AnalyseRow.jsx src/components/CreateWorksheetModal.jsx src/components/WorksheetTimesModal.jsx src/components/WorksheetList.jsx docs/superpowers/plans/2026-10-05-weisses-design-p1-tote-klassen.md
git commit -m "Design: Pflicht-Fixes gegen helle Schrift und dunkle Flaechen, Diagrammfarben, Chat-Variablen" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 10: Gate P1, 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. Die Testzahl ist 312 − 3 (`sidebar`) + 1 (`statusTone`) + 2 (`uiTones`) + 1 (`dialogKeys`) = 313. Liegen neue Commits auf `test`, kann sie höher sein; die Zahl wird notiert.
- [ ] **Step 2: Vollständiger Gate-Lauf**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p1 && "$W/shots.sh" p1 "$W/shots/p1" --gate p1`
Expected: Exit 0 für alle Ansichten aus P0 plus `basis-weiss`, `menue-mobil` und `dlg-probe`. Alle `dlg-*` bestehen `submitReachable` bei 700 px Höhe.
- [ ] **Step 3: Bundle-Größe**
Die Werte aus der `check.sh`-Ausgabe werden mit P0 verglichen. Das CSS sollte kleiner sein, und die Schrift fehlt.
- [ ] **Step 4: Code-Review (sonnet)**
Der Diff `603d08c..HEAD` (inklusive P0) wird gegen die Spec geprüft, Abschnitte:
- „Gestaltungsregeln“
- „Bausteine“
- „KOMPAT“
- Pakete P0 und P1
Wichtige Befunde werden behoben. Danach kommt der Rebase aus Step 1 noch einmal und Step 2 als Kurzlauf mit `--only tickets,login`.
- [ ] **Step 5: Tag und Übergabe**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" && git tag design-p1 && git log --oneline origin/test..design-p1 && git rev-parse design-p1`
Übergabe an den Eigentümer:
- Screenshots aus `$W/shots/p1` (Login, Tickets, Kunde, Projekte, Finanzen, Serverpuls, ein Alt-Dialog, Menü mobil)
- Hinweis zu den neuen Statusfarben für das Team
- Push-Zeile `git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p1^{commit}:refs/heads/test'`
- Rücknahme: `git revert --no-commit 603d08c..design-p1`, Commit und Push durch den Eigentümer
- [ ] **Step 6: Nach dem Push (erst, wenn der Eigentümer gepusht hat)**
- Im Log von `webklar-ticket-build` steht „Deployment completed successfully“.
- `curl -s https://ticket.webklar.com/ | grep -o 'assets/index-[^"]*\.js'` stimmt mit `dist/index.html` des Tags überein.
- Ein lesender Sichttest im Chrome des Eigentümers: Login-Seite und Ticketliste.