# 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 " ``` ### 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: `
`. - Kopf: `
` ohne `sp-level-*`. Die Kopfzeile `div.sp-head-top` enthält: - `{LEVEL_TEXT[level] || level}`. `LEVEL_TEXT` ist die schon vorhandene, bisher ungenutzte Konstante („Alles ok“, „Warnungen“, „Critical“), das Emoji und `dot` entfallen. - die `` 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 `{LEVEL_LABEL[a.level] || a.level}`, `span.sp-alert-title` und `span.sp-alert-value`. - Rechts `seit {formatSince(a.since)}`. - [ ] **Step 3: Kacheln, Umschalter, Panels, Aufräumbar** - Kacheln: `
`. Jede Kachel ist `div.sp-tile[data-testid="sp-tile-"]` 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 `
` mit `