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

391 lines
29 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“ P9: Serverpuls – 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 P9 ist Serverpuls hell und kastenlos. Der Kopf trägt ein Text-Badge statt eines Emojis, Warnungen sind Listenzeilen mit Stufen-Badge, die Kacheln sind `.stats` mit Sparkline. Deltas zeigen sich als Textfarbe, Umschalter sind `.seg`, Panels sind `h2.section`, die Projekttabelle ist eine `.table`. Die Diagramme nutzen die Farben aus `chartColors.js`. Alle `data-testid` bleiben.
**Architecture:**
- Drei reine Helfer in `src/lib/serverpulsFormat.js` mit Tests: `deltaDir`, `deltaSign`, `levelTone`.
- Bausteine aus P1: `Stat`, `Badge` (`badgeToneClass` aus `ui/tones.js`), `PageHeader`, `.stats`, `.list-row`, `.seg`, `.table`, `.msg`, `.toolbar`, `.input.search`.
- `serverpuls.css` wird neu geschrieben (74 → rund 70 Zeilen).
- `Chart.jsx` und `Sparkline.jsx` bleiben unverändert, weil P1 die Diagrammfarben schon umgestellt hat. Dasselbe prüft Task 6.
- Abfragen, 60-Sekunden-Takt, Neuladen bei Sichtbarkeit, `reqId`-Schutz, Sortierlogik und Zeitachsen bleiben unverändert.
**Tech Stack:** React 18, uPlot (Canvas), reines CSS. Tests mit `node --test`, Screenshot-Werkzeug aus P0.
**Spec:** `docs/superpowers/specs/2026-10-04-weisses-design-design.md`, Abschnitte „Gestaltungsregeln“, „Design-Tokens“ (Diagrammfarben), „Bausteine“ (Kennzahlen, Umschalter, Tabelle, Liste, Badge, Meldungen, Laden) und Paket P9. Anhang `2026-10-04-weisses-design-umbaukarte.md`: Abschnitt 5 (P9) und 7.1. Globale Regeln und Schnittstellen: `2026-10-05-weisses-design-00-uebersicht.md`.
## Global Constraints
Es gelten die Regeln der Übersicht. Für P9 kommt hinzu:
- 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`. Tests und Build werden nur über `"$W/check.sh"` ausgewertet, Browser-Skripte laufen nur über `"$W/shots.sh"`.
- Ausgangspunkt ist das Tag `design-p8`, alle Diff-Prüfungen vergleichen gegen `design-p8`.
- CSS-Reihenfolge: `main.jsx` importiert `App` vor `global.css`, also lädt `serverpuls.css` vor `global.css`, und bei gleicher Spezifität gewinnt `global.css`. Jede Regel in `serverpuls.css`, die eine Global-Klasse überstimmt (`.stats`, `.list-row`, `.msg`, `.table`, `.seg`), hat höhere Spezifität (0,2,0, z. B. `.stats.sp-tiles`, `.sp-page .msg`) und nie `!important`.
- Abstände zwischen aufeinanderfolgenden Abschnitten kommen von `.page section + section { margin-top: 36px }` aus P1. P9 baut dafür keine eigenen Regeln: `.sp-panel` und `.stats.sp-tiles` setzen keinen Außenabstand nach oben. Eigene Abstände gibt es nur innerhalb der Abschnitte (`div`-Container).
- Die CSS-Tabellen dieses Plans nennen nur die Abweichungen vom Prototyp und von den Tokens. Was Basis (`*`-Reset, `button`-Reset), `.table`, `.stat`, `.list-row` und `.seg` schon liefern, wird nicht wiederholt.
- Canvas-Farben stehen nur in `chartColors.js`. In `ServerpulsPage.jsx`, `Chart.jsx`, `AppsTable.jsx` und `Sparkline.jsx` gibt es kein Farbliteral.
- Keine Icon-Importe, keine Emojis, keine Glyphen ▲ und ▼. Die Sortierzeichen ↑ und ↓ bleiben.
- Sichtbare Textänderungen: keine außer den Badge-Texten des Kopfes aus `LEVEL_TEXT`. Alle Ladetexte („Lädt …“) und Meldungen bleiben wörtlich.
- Keine neuen `style={{…}}`.
## Review Focus
Aus der Übersicht gehören drei Punkte zu P9:
- **Punkt 2, lange Inhalte am Handy:** Sechs Kacheln, Umschalter mit 6 und 5 Knöpfen und die Tabelle bei 390 px ohne waagerechtes Scrollen. Testdaten `serverpuls-lang`: Projektname mit 60 Zeichen ohne Leerzeichen, Warnungstitel mit 90 Zeichen. `overflow-x` ist seit dem Gate `p1` für alle Ansichten scharf, also auch im Gate `p9`.
- **Punkt 3, Teilausfall:** Ansicht `serverpuls-ausfall`. Die Übersicht antwortet 500, `sp-error` erscheint, Diagramm und Tabelle bleiben nutzbar, die Kacheln zeigen „–“.
- **Punkt 4, nur Tastatur:**
- Projektzeilen lassen sich über den Button in der ersten Zelle öffnen (`aria-pressed`).
- Sortierköpfe sind Buttons mit `aria-sort` auf dem `th`.
- Die `.seg`-Knöpfe tragen `aria-pressed`.
Zusätzlich gilt für P9:
- Checkliste der Bedienelemente, nichts darf fehlen: Metrik-Umschalter (6 Knöpfe), Zeitraum-Umschalter (5), Projektsuche, 6 Sortierköpfe (Projekt, Größe, Δ 7 T, Δ 30 T, CPU, RAM), Projektzeile (öffnet und schließt das Detail), Zeitraum-Umschalter im Detail (5).
- Die 20 `data-testid` im Serverpuls-Code bleiben identisch (Task 1 und 6). Ohne den Muster-Eintrag `sp-tile-${id}` sind es die 19 der Spec.
- Rot heißt gestiegen und Grün gesunken, das Vorzeichen steht weiter in der Zahl.
---
### Task 1: Paketstart und Ausgangswerte
**Files:** keine Änderung.
**Interfaces:** keine.
- [ ] **Step 1: Paketstart**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git fetch origin && git merge-base --is-ancestor design-p8 HEAD && git rebase origin/test && "$W/check.sh" && git status --short`
Expected: Exit 0, Rebase ohne Konflikt, `# fail 0` und eine leere Statusliste. Endet `merge-base` mit Exit 1, liegt `design-p8` nicht im Branch: stoppen und melden. Die Testzahl heißt im Folgenden N0, die Bundle-Größen aus der Ausgabe von `check.sh` werden notiert.
- [ ] **Step 2: Abgleich mit TP2**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git log --oneline origin/test..feature/terminplaner-tp2 -- src/pages/ServerpulsPage.jsx src/components/serverpuls src/styles/serverpuls.css src/lib/serverpulsFormat.js src/styles/global.css`
Expected: keine Ausgabe. Gibt es Treffer, stoppt der Agent vor Task 2, nennt dem Eigentümer die Commits und fragt nach. Er empfiehlt dabei, TP2 zuerst nach `test` zu bringen.
- [ ] **Step 3: Referenzliste der Test-IDs anlegen**
Run: ``source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -o --no-filename 'testid=("sp-[a-z-]+"|\{`sp-tile-\$\{id\}`\})' src/pages/ServerpulsPage.jsx src/components/serverpuls | sort > "$W/p9-testids.before.txt" && wc -l < "$W/p9-testids.before.txt"``
Expected: `20`.
- [ ] **Step 4: Voraussetzungen aus P1 und P2 prüfen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && ls src/components/serverpuls/chartColors.js src/components/ui/Stat.jsx src/components/ui/tones.js src/lib/rowClick.js && rg -n "isRowActivation" src/lib/rowClick.js && rg -n -m 1 "CHART\." src/components/serverpuls/Chart.jsx src/pages/ServerpulsPage.jsx && rg -n "#[0-9a-fA-F]{3,8}\b|rgba?\(" src/components/serverpuls src/pages/ServerpulsPage.jsx --glob '!chartColors.js'`
Expected: Die Dateien sind da, `src/lib/rowClick.js` (P2 Task 2) enthält `isRowActivation(event)`, `CHART.` kommt in beiden Dateien vor, und die letzte Suche liefert keine Treffer (Exit 1). Fehlt `rowClick.js`, stoppt der Agent und meldet es, P9 baut keinen eigenen Zeilenklick-Helfer.
### Task 2: Format-Helfer `deltaDir`, `deltaSign`, `levelTone` (TDD)
**Files:**
- Modify: `src/lib/serverpulsFormat.js` (neue Exporte nach `formatNumber`, vor dem Abschnitt `// --- Zeitachsen`)
- Test: `tests/unit/serverpulsFormat.test.mjs` (Z. 5 Import, zwei neue Tests)
**Interfaces:**
- Produces:
- `deltaDir(d) → 'up' | 'down' | 'flat'` (`flat` bei Betrag unter `1e-9`, genau die Regel aus `Tile`).
- `deltaSign(dir) → '+' | '−' | ''` (U+2212).
- `levelTone(level) → 'ok' | 'warn' | 'danger' | 'muted'`. Es sind die Ton-Namen von `Badge`.
- Bestehende Exporte bleiben unverändert.
- [ ] **Step 1: Failing Test schreiben**
Die Destrukturierung in Z. 5 bekommt `deltaDir, deltaSign, levelTone`. Dazu:
```js
test('deltaDir und deltaSign: Richtung und Vorzeichen der Kachel-Deltas', () => {
assert.deepEqual([deltaDir(0), deltaDir(5e-10), deltaDir(-5e-10)], ['flat', 'flat', 'flat'])
assert.equal(deltaDir(0.4), 'up')
assert.equal(deltaDir(-0.4), 'down')
assert.deepEqual([deltaSign('up'), deltaSign('down'), deltaSign('flat')], ['+', '−', ''])
})
test('levelTone: Badge-Ton je Stufe', () => {
assert.deepEqual(['ok', 'medium', 'critical'].map(levelTone), ['ok', 'warn', 'danger'])
assert.deepEqual([levelTone('low'), levelTone(undefined), levelTone('x')], ['muted', 'muted', 'muted'])
})
```
- [ ] **Step 2: Test laufen lassen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/serverpulsFormat.test.mjs`
Expected: FAIL, `deltaDir is not a function`.
- [ ] **Step 3: Implementieren**
Die drei Funktionen in `src/lib/serverpulsFormat.js`. Die Schwelle `1e-9` und das Zeichen U+2212 sind wie in `Tile` und `formatDelta`.
- [ ] **Step 4: Test grün**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && node --test tests/unit/serverpulsFormat.test.mjs && "$W/check.sh" --nur-tests`
Expected: PASS, Exit 0, Testzahl N0 + 2, `# fail 0`.
- [ ] **Step 5: Commit**
```bash
git add src/lib/serverpulsFormat.js tests/unit/serverpulsFormat.test.mjs
git commit -m "Serverpuls: Richtung, Vorzeichen und Stufen-Ton als Format-Helfer mit Test" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 3: Werkzeug – Testdaten, Ansichten, Gate `p9`, Ausnahme für Sparklines
**Files** (alle unter `$W/`, kein Commit im Repo):
- Create: `fixtures/serverpuls-ok.mjs`, `fixtures/serverpuls-veraltet.mjs`, `fixtures/serverpuls-lang.mjs`
- Modify: `views.mjs`, `gates.mjs`, `checks.mjs`
**Interfaces:**
- Consumes: `createBackend`, `VIEWS`, `GATES` aus P0 und die Konsolen-Ausnahme des Szenarios `fehler` (P0, in P8 Task 4 Step 3 erweitert).
- Produces: die Ansichten aus Step 2, `GATES.p9`, die Ausnahme `svg.sp-spark` im Check `icon-svg` und die Ausnahme für `**/api/serverpuls/**` im Szenario `fehler`.
- [ ] **Step 1: Testdaten-Varianten anlegen**
Alle erweitern `standard`.
- `serverpuls-ok`: Übersicht mit `level: 'ok'` und `alerts: []`.
- `serverpuls-veraltet`: `level: 'medium'`, eine Warnung, `lastSample` vor 40 Minuten.
- `serverpuls-lang`: `level: 'critical'` mit drei Warnungen (`critical`, `medium`, `low`), davon eine mit 90-Zeichen-Titel. Zwölf Projektzeilen, eine mit 60-Zeichen-Namen ohne Leerzeichen.
- [ ] **Step 2: Ansichten in `views.mjs` anlegen oder anpassen**
Der `path` ist `/serverpuls`. Die Schritte haben die Signatur `steps(page, ctx)`, `ctx.backend.calls` listet die vom Backend beantworteten Requests. Geprüft wird mit `throw new Error(…)`, das den Verstoß `step-failed` erzeugt.
| ID | Daten | Schritte |
|---|---|---|
| `serverpuls` | `serverpuls-lang` | Warten, bis `[data-testid="sp-chart"]` ein `canvas` mit Breite über 0 trägt und sechs `[data-testid^="sp-tile-"]` da sind. |
| `serverpuls-ok` | `serverpuls-ok` | Wie `serverpuls`. |
| `serverpuls-veraltet` | `serverpuls-veraltet` | Wie `serverpuls`. |
| `serverpuls-projekt` | `serverpuls-lang` | Fokus auf `button.link` der ersten `[data-testid="sp-app-row"]`, Enter: `sp-detail` muss sichtbar sein und drei `canvas` enthalten. Fokus auf den Sortierkopf „Größe“ (`th[data-sort="sizeBytes"] button`), Enter: `aria-sort` muss `ascending` heißen. Klick auf „RAM“ in `[data-testid="sp-metric-select"]` (nicht auf den gleichnamigen Tabellenkopf), dann muss dieser Knopf `aria-pressed="true"` tragen. |
| `serverpuls-ausfall` | `standard`, Szenario `fehler` | `GET **/api/serverpuls/overview` mit 500 `{ error: 'Test: Serverpuls ist nicht erreichbar.' }` beantworten. `sp-error` muss sichtbar sein, das Diagramm gezeichnet und die Tabelle da. |
- [ ] **Step 3: Gate festlegen**
In `gates.mjs` kommt nach der Deklaration von `GATES` dazu. Das Gate `p9` erbt Prüfungen und Ansichten von `p8` (die `p1`-Prüfungen für alle Ansichten, `step-failed`, `external-request` und `console-error` immer) und schaltet `icon-svg` zusätzlich für die Ansichten von P9 und aller früheren Pakete ein:
```js
const iconSvg = GATES.p8.scoped['icon-svg']
GATES.p9 = {
...GATES.p8,
scoped: {
...GATES.p8.scoped,
'icon-svg': {
...iconSvg,
views: [...iconSvg.views, 'serverpuls', 'serverpuls-ok', 'serverpuls-veraltet', 'serverpuls-projekt', 'serverpuls-ausfall'],
},
},
}
```
- [ ] **Step 4: Sparklines vom Icon-Check ausnehmen**
`checks.mjs`: Der Check `icon-svg` nimmt `svg.sp-spark` dauerhaft aus (wie `.brand`), nicht über `ignore` im Gate. Es sind Datengrafiken (Inline-SVG-Minikurven), keine Icons. Die Konsolen-Ausnahme des Szenarios `fehler` deckt auch die Meldungen zu `**/api/serverpuls/**` ab, falls sie das noch nicht tut.
- [ ] **Step 5: Ausgangslauf**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p9-vorher && "$W/shots.sh" p9-vorher "$W/shots/p9-vorher" --only serverpuls --gate p9`
Expected: Exit 0. Der Stand nach P1 ist hell, und die Sparklines sind ausgenommen. Die Bilder dienen als Vergleich.
### Task 4: Kopf, Warnungen, Kacheln, Umschalter, Panels
**Files:**
- Modify: `src/pages/ServerpulsPage.jsx` (`LEVEL_*` Z. 31–32, `Tile` Z. 79–100, `Pills` Z. 102–117, `Cleanup` Z. 119–139, Markup Z. 254–331; Abfragen und Hooks Z. 141–239 bleiben)
- Rewrite: `src/styles/serverpuls.css` (ohne die Tabellenregeln, die bleiben bis Task 5)
**Interfaces:**
- Consumes: `Stat({ label, value, sub, tone })`, `PageHeader`, `badgeToneClass(tone)` aus `src/components/ui/tones.js`, `Badge`, `deltaDir`, `deltaSign`, `levelTone` (Task 2), `formatDelta`.
- Produces: nichts für spätere Pakete.
- [ ] **Step 1: Kopf und Fehlerzeile**
- Fehler: `<div className="msg err" data-testid="sp-error" role="alert">`.
- Kopf: `<section data-testid="sp-head">` ohne `sp-level-*`. Die Kopfzeile `div.sp-head-top` enthält:
- `<span className={badgeToneClass(levelTone(level))} data-testid="sp-ampel" data-level={level}>{LEVEL_TEXT[level] || level}</span>`. `LEVEL_TEXT` ist die schon vorhandene, bisher ungenutzte Konstante („Alles ok“, „Warnungen“, „Critical“), das Emoji und `dot` entfallen.
- die `<strong className="sp-headline">` mit dem unveränderten Text und `sp-age`. Bei `age.stale` kommt zusätzlich die Klasse `sp-age-stale`.
- Ohne `overview` zeigt das Badge `badge plain` mit „–“, `data-level` bleibt `ok`.
Entscheidung: Das Badge nennt die Stufe aus `LEVEL_TEXT`, die Überschrift bleibt wörtlich („Alles ok“, „Critical – 2 Warnungen“, „2 Warnungen“). Die Doppelung ist gewollt. Ohne Daten steht „–“ statt „Alles ok“, damit der Fehlerzustand nicht grün aussieht.
- [ ] **Step 2: Warnungen**
`ul.sp-alerts[data-testid=sp-alerts]` mit `li.list-row.sp-alert[data-testid=sp-alert]`:
- Links `div.sp-alert-main` mit `<Badge tone={levelTone(a.level)}>{LEVEL_LABEL[a.level] || a.level}</Badge>`, `span.sp-alert-title` und `span.sp-alert-value`.
- Rechts `<span className="muted small nowrap">seit {formatSince(a.since)}</span>`.
- [ ] **Step 3: Kacheln, Umschalter, Panels, Aufräumbar**
- Kacheln: `<section className="stats sp-tiles" data-testid="sp-tiles">`. Jede Kachel ist `div.sp-tile[data-testid="sp-tile-<id>"]` mit `Stat label value sub`, `Sparkline width={120} height={32}` und der Delta-Zeile.
- `sub` ist `model?.sub || '\u00a0'`, damit die Sparklines einer Reihe fluchten.
- Die Delta-Zeile ist `div.sp-tile-delta.sp-delta-{dir}` mit `{deltaSign(dir)}{model.fmtDelta(d)} ggü. gestern`. `dir` kommt aus `deltaDir(d)`, die Glyphen ▲ und ▼ entfallen.
- `Pills` rendert `<div className="seg" data-testid={testid}>` mit `<button type="button" className={active ? 'on' : undefined} aria-pressed={active}>`.
- Panels: `<section className="sp-panel" data-testid="sp-chart-panel">` mit `<h2 className="section">Verlauf</h2>`, darunter `div.sp-controls` mit beiden `Pills`, dann das Diagramm.
- `sp-projects` und `sp-cleanup` folgen demselben Muster.
- „Größen von {Tag}“ steht als `<span className="muted small">` in `h2.section` rechts.
- Die Detail-Überschrift ist `<h3 className="sp-detail-title">`, ihre `Pills` stehen in `div.sp-controls`.
- „Aufräumbar“: `div.stats.sp-cleanup-stats` mit drei `Stat` („Ungenutzte Images“, „Build-Cache“, „Gestoppte Container“), die Liste der gestoppten Container als `li.list-row` mit Name und `<span className="num">`-Größe, der Stand-Satz als `<p className="muted small">`. „Noch keine Messung.“ bekommt `.muted`.
- Alle `sp-muted`, `sp-small` werden `muted`, `small`. `.sp-nodata` bleibt als Klasse für „Lädt …“ und „Für diesen Zeitraum …“.
Entscheidung: Die Panels sind `<section data-testid>` mit `h2.section` statt `Card`, weil `Card` kein `data-testid` weitergibt. Die Umschalter stehen unter der Überschrift, nicht darin, damit der Kopf auf dem Handy nicht überläuft.
- [ ] **Step 4: `serverpuls.css` neu schreiben**
Gelöscht werden `.sp-page`-Flex, alle Kästen, Rahmen und Literale, `.sp-level-*`, `.sp-ampel`, `.sp-tile-label/-value/-sub`, `.sp-pill*`, `.sp-panel-head`, `.sp-cleanup-grid`. Neu:
| Regel | Wert |
|---|---|
| `.sp-page .msg` | `margin: 0 0 16px` |
| `.sp-head-top` | `display: flex; align-items: center; gap: 12px; flex-wrap: wrap` |
| `.sp-headline` | `font-size: 16px; line-height: 24px; font-weight: 600` |
| `.sp-age` | `margin-left: auto; font-size: 13px; color: var(--text-2)` |
| `.sp-age-stale` | `color: var(--orange-fg); font-weight: 500` |
| `.sp-alerts` | `list-style: none; margin: 12px 0 0; padding: 0` |
| `.sp-alert-main` | `display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; min-width: 0` |
| `.sp-alert-title` | `font-weight: 500; overflow-wrap: anywhere` |
| `.sp-alert-value` | `color: var(--text-2)` |
| `.stats.sp-tiles` | `display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 24px 32px` (ohne `margin`) |
| `.sp-tile` | `min-width: 0` |
| `.sp-tile .sub` | `min-height: 16px` (Größe und Farbe kommen von `.stat .sub`) |
| `.sp-spark` | `display: block; max-width: 100%; margin-top: 6px` |
| `.sp-tile-delta` | `font-size: 12px; line-height: 16px; margin-top: 4px` |
| `.sp-delta-up` / `-down` / `-flat` | `color: var(--red-fg)` / `var(--green-fg)` / `var(--text-2)` |
| `.sp-panel` | `min-width: 0` (ohne `margin`) |
| `.sp-controls` | `display: flex; flex-wrap: wrap; gap: 8px 16px; margin-bottom: 12px` |
| `.sp-controls .seg` | `flex-wrap: wrap` |
| `.sp-chart` | `width: 100%; min-height: 200px` |
| `.sp-chart .u-legend` | `font-size: 13px; color: var(--text-2)` |
| `.sp-nodata` | `padding: 40px 0; text-align: center; color: var(--text-2)` |
| `.sp-detail` | `margin-top: 28px` (ein `div` im Panel, keine `section`) |
| `.sp-detail-title` | `margin: 0 0 8px; font-size: 15px; line-height: 22px; font-weight: 600; overflow-wrap: anywhere` |
| `.sp-detail-charts` | `display: grid; grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr)); gap: 24px` |
| `.sp-chart-title` | `font-size: 13px; color: var(--text-2); margin-bottom: 4px` |
| `.sp-cleanup-list` | `list-style: none; margin: 12px 0; padding: 0` |
| ≤ 1100 px `.stats.sp-tiles` | `grid-template-columns: repeat(3, minmax(0, 1fr))` |
| ≤ 860 px `.stats.sp-tiles` | `grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 24px` |
| ≤ 860 px `.sp-age` | `margin-left: 0` |
| ≤ 860 px `.sp-page .sp-cleanup-stats` | `grid-template-columns: repeat(2, minmax(0, 1fr))` |
Den Abstand zwischen Kopf, Kacheln und Panels liefert `.page section + section` aus P1. `.stats.sp-tiles` und `.sp-panel` setzen deshalb keinen oberen Außenabstand. Ein `margin` auf `.stats.sp-tiles` würde den globalen Abstand wegen der höheren Spezifität überstimmen.
- [ ] **Step 5: Bauen und prüfen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p9 && "$W/shots.sh" p9 "$W/shots/p9-t4" --only serverpuls,serverpuls-ok,serverpuls-veraltet,serverpuls-ausfall --gate p9`
Expected: PASS. In den Bildern gilt:
- Keine Kästen um Kopf, Kacheln und Panels.
- Das Badge trägt „Critical“ (rot), „Warnungen“ (orange) bzw. „Alles ok“ (grün). Veraltete Daten stehen als oranger Text.
- Warnungen sind Zeilen mit Haarlinien und Stufen-Badge (Critical rot, Medium orange, Low grau).
- Die Kacheln stehen bei 1440 px in sechs Spalten, bei 390 px in zwei. Deltas sind rot oder grün ohne Pfeil.
- Der Umschalter ist grau mit weißem aktivem Teil und bricht bei 390 px um.
- Das Diagramm hat eine schwarze Linie, die Spitze ist grau gestrichelt, es gibt nur horizontale Rasterlinien, und die Legende ist 13 px grau.
- Kopf, Kacheln und Panels haben überall denselben Abstand (36 px aus `.page section + section`).
- [ ] **Step 6: Commit**
```bash
git add src/pages/ServerpulsPage.jsx src/styles/serverpuls.css
git commit -m "Design: Serverpuls-Kopf mit Text-Badge, Warnungen als Zeilen, Kacheln als Kennzahlen" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 5: Projekttabelle
**Files:**
- Modify: `src/components/serverpuls/AppsTable.jsx` (Spaltenliste Z. 5–12, `Delta` Z. 14–18, Markup Z. 42–89; Sortier- und Filterlogik Z. 20–40 bleibt)
- Modify: `src/styles/serverpuls.css` (Tabellenregeln `.sp-search` bis `.sp-delta` ersetzen)
**Interfaces:**
- Consumes: `.table`, `tr.row`, `.toolbar`, `.input.search`, `.hide-sm`, `.num`, `.right` (P1), `isRowActivation(event)` aus `src/lib/rowClick.js` (P2 Task 2, Prüfung in Task 1), `formatBytes`, `formatDelta`, `formatPct`, `Sparkline`.
- Produces: `AppsTable({ rows, selectedId, onSelect })` behält seine Props.
- [ ] **Step 1: Markup umbauen**
- Die Wurzel `div.sp-apps[data-testid=sp-apps]` bleibt. Die Suche steht in `<div className="toolbar">` als `<input className="input search" type="search" data-testid="sp-apps-search" placeholder="Projekt suchen …">`.
- `<table className="table">` ohne `.sp-table-wrap`. Die Spaltenliste bekommt je Eintrag `num` (Größe, Δ 7 T, Δ 30 T, CPU, RAM) und `hide` (Δ 30 T, CPU und die Spalte „30 Tage“). Diese Spalten tragen `right num` bzw. `hide-sm` auf `th` und `td`.
- Sortierkopf: `<th data-sort={c.key} aria-sort={…}>` mit `<button type="button" className="sp-sort" onClick={() => toggle(c.key)}>{c.label}<span aria-hidden="true">{' ↑' | ' ↓' | ''}</span></button>`.
- `aria-sort` ist `ascending` bzw. `descending` am aktiven Kopf, sonst fehlt es.
- Die Zeichen ↑ und ↓ bleiben wie bisher.
- Zeile: ``<tr className={`row${selected ? ' sp-row-active' : ''}`} data-testid="sp-app-row" data-app-id={r.id}>``.
- Der Zeilenklick ruft `onSelect(selectedId === r.id ? null : r.id)`, aber nur wenn `isRowActivation(e)` wahr ist.
- Die erste Zelle ist `<td className="sp-name"><button type="button" className="link" aria-pressed={selected} onClick={() => onSelect(selectedId === r.id ? null : r.id)}>{r.name}</button></td>` mit demselben Aufruf. Der Button braucht ihn, weil `isRowActivation` Klicks auf `button` ausfiltert.
- `Delta` rendert ``<span className={`sp-delta sp-delta-${d.dir}`}>{d.text}</span>`` ohne Pfeil. Das Vorzeichen steckt in `d.text`.
- Die Leerzeile bleibt (`colSpan={7}`, „Keine Projekte gefunden.“, Klasse `sp-empty`).
Entscheidung: Die Zeilen folgen der Tabellenregel der Spec. Der Button in der ersten Zelle trägt die Tastaturbedienung, der Zeilenklick bleibt für die Maus. Unter 860 px bleiben Projekt, Größe, Δ 7 T und RAM.
- [ ] **Step 2: Tabellen-CSS ersetzen**
Gelöscht werden `.sp-search`, `.sp-table-wrap`, `.sp-table*`, `.sp-sortable`, `.sp-row`, `.sp-row:hover`, `.sp-delta`. Neu:
| Regel | Wert |
|---|---|
| `.sp-sort` | `display: block; width: 100%; text-align: inherit; white-space: nowrap` (Knopf-Reset und Innenabstand 0 kommen aus der Basis) |
| `.sp-name` | `overflow-wrap: anywhere` |
| `.sp-name .link` | `text-align: left; overflow-wrap: anywhere` |
| `.sp-page .table tr.sp-row-active td` | `background: var(--bg-subtle)` |
| `.sp-page .table tr.sp-row-active td:first-child` / `:last-child` | `box-shadow: -12px 0 0 var(--bg-subtle)` / `12px 0 0 var(--bg-subtle)` |
| `.sp-empty` | `text-align: center; color: var(--text-2)` |
| `.sp-delta` | `font-variant-numeric: tabular-nums` |
- [ ] **Step 3: Bauen und prüfen**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p9 && "$W/shots.sh" p9 "$W/shots/p9-t5" --only serverpuls,serverpuls-projekt --gate p9`
Expected: PASS, das heißt:
- Die Tastaturschritte laufen durch.
- Die Tabelle hat nur Haarlinien, Zahlen stehen rechts, die ausgewählte Zeile ist `--bg-subtle`.
- Deltas sind rot, grün oder grau ohne Pfeil. Das Detail zeigt drei Diagramme in Schwarz.
- Bei 390 px sind vier Spalten da und der lange Projektname bricht um.
- [ ] **Step 4: Commit**
```bash
git add src/components/serverpuls/AppsTable.jsx src/styles/serverpuls.css
git commit -m "Design: Serverpuls-Projekttabelle (Sortierkoepfe und Zeilen als Buttons, Deltas als Farbe)" -m "Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>"
```
### Task 6: Gate P9, 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, Rebase ohne Konflikt, `# fail 0`. Die Testzahl ist N0 + 2. Liegen neue Commits auf `test`, kann sie höher sein, die Zahl wird notiert. So zeigen die Bilder der folgenden Schritte den Stand, der getaggt wird.
- [ ] **Step 2: Vollständiger Gate-Lauf**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/build.sh" "$WT" p9 && "$W/shots.sh" p9 "$W/shots/p9" --gate p9`
Expected: Exit 0. Das Gate `p9` prüft alle Ansichten mit den `p1`-Prüfungen (`overflow-x`, `dark-surface`, `low-contrast`) und `icon-svg` für die Ansichten von P2 bis P9. Die fünf P9-Ansichten haben Bilder bei 1440 und 390 px ohne Verstöße.
- [ ] **Step 3: Test-IDs, Farben, unveränderte Logik**
Run: ``source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -o --no-filename 'testid=("sp-[a-z-]+"|\{`sp-tile-\$\{id\}`\})' src/pages/ServerpulsPage.jsx src/components/serverpuls | sort | diff - "$W/p9-testids.before.txt"``
Expected: keine Ausgabe, also dieselben 20 Einträge.
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && rg -n "react-icons|@tabler|style=\{|#[0-9a-fA-F]{3,8}\b|rgba?\(|!important|▲|▼|🔴|🟠|🟢" src/pages/ServerpulsPage.jsx src/styles/serverpuls.css src/components/serverpuls --glob '!chartColors.js'`
Expected: keine Treffer.
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && git diff design-p8 -- src/pages/ServerpulsPage.jsx | rg '^[-+].*(setInterval|60000|visibilit|reqId|serverpulsApi)'; git diff --stat design-p8 -- src/components/serverpuls/Chart.jsx src/components/serverpuls/Sparkline.jsx`
Expected: beide Ausgaben leer, weil Takt, Abfragen, `Chart.jsx` und `Sparkline.jsx` unverändert sind.
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.dead.filter(n=>/^sp-/.test(n)).concat(j.undefinedUsed.filter(n=>/^sp-/.test(n))).join('\n'))"`
Expected: leer, also keine tote und keine undefinierte `sp-*`-Klasse.
- [ ] **Step 4: Bundle-Größe**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" --nur-build`
Expected: Exit 0, CSS kleiner als bei Task 1. Die Werte werden notiert.
- [ ] **Step 5: Code-Review (sonnet)**
Geprüft wird der Diff `design-p8..HEAD` gegen die Spec-Abschnitte „Diagrammfarben“, „Bausteine“ (Kennzahlen, Umschalter, Tabelle, Liste, Badge, Meldungen) und Paket P9. Wichtige Befunde werden behoben. Danach kommen Step 1 (Rebase) noch einmal und Step 2 als Kurzlauf mit `--only serverpuls,serverpuls-projekt`. Schwerpunkte:
- Die Test-IDs und Abfragen sind unverändert.
- Der Fehlerzustand zeigt kein grünes „Alles ok“.
- Das Vorzeichen steht weiter in jeder Delta-Zahl.
- Die Tastaturbedienung funktioniert (Zeilen, Sortierköpfe, Umschalter).
- [ ] **Step 6: Tag und Übergabe**
Run: `source /root/reports/ticketsystem-design-2026-10-04/werkzeug/env.sh && "$W/check.sh" && git tag design-p9 && git log --oneline origin/test..design-p9 && git rev-parse design-p9`
Der Eigentümer bekommt:
- die Screenshots aus `$W/shots/p9` (Unterordner `1440x900` und `390x844`; Auswahl: `serverpuls`, `serverpuls-ok`, `serverpuls-projekt`, `serverpuls-ausfall`)
- den Hinweis, dass das Kopf-Badge „Alles ok“ und die Überschrift „Alles ok“ doppelt stehen (Entscheidung aus Task 4), falls er die Doppelung streichen will
- die Push-Zeile `git -C /home/webklar/apps/ticket.webklar.com/dev-design push origin 'design-p9^{commit}:refs/heads/test'`
- die Rücknahme `git revert --no-commit design-p8..design-p9`, Commit und Push durch den Eigentümer (gilt nur, solange P9 das jüngste Paket auf `test` ist)
- [ ] **Step 7: 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'` entspricht `dist/index.html` des Tags.
- Ein lesender Sichttest im Chrome des Eigentümers: Serverpuls mit Diagramm und Projekttabelle. Der Menüpunkt zeigt weiter den Punkt `sp-nav-dot`, wenn es Warnungen gibt.