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

198 lines
20 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.
# P1 Task 4: tote Klassen und Übergangsschicht KOMPAT
Ergebnis des Klassen-Abgleichs mit `css-klassen.mjs` (Werkzeug-Repo `$W`). Zeilenangaben beziehen sich auf `global.css` im Stand `a6877aa` (vor diesem Commit). Spec: Abschnitt „Übergangsschicht KOMPAT“.
## Werkzeug
`node "$W/css-klassen.mjs" "$WT" [--ohne-ausnahmen] [--mit-neu]` gibt JSON aus: `{ defined, used, dead, undefinedUsed }` und dazu `definedAt` (Name → Datei:Zeile), `prefixes`, `ausnahmen`, `laufzeit`, `neuOhneBenutzer`, `counts`.
- **Definiert:** Klasse in einem Selektor in `global.css`, `chat.css`, `serverpuls.css`, `admin-areas.css` oder `pages/AdminPage.css` (Regeln in `@media` und `@container` zählen, `@keyframes` nicht). Geparst mit `postcss`.
- **Benutzt:** Wort in einem `className`-Ausdruck in `src/**/*.jsx` (Literale, Template-Strings, Zweige von `?:`, rechte Seite von `&&`, Teile einer `+`-Kette; Bedingungen und Vergleiche wie `filter === 'all'` zählen nicht; ein Ergebnis, das an Text klebt wie `fin-todo-${x || 'info'}`, ist nur ein Namensteil). Dazu Wörter aus Zeichenketten in `src/lib/*.js` und `src/hooks/*.js`, die eine definierte Klasse sind. Geparst mit `@babel/parser`.
- **Präfixe:** `xxx-${…}` oder `'xxx-' + …` deckt `xxx-*`.
- **Ausnahmeliste** (entfällt mit `--ohne-ausnahmen`): `fin-neg`, `fin-fixed-off`, `legal-tone-*`, `legal-version-*`, `badge-*`, `status-*`, `ticket-tab-active`, `ui-btn-*`, `side-dot-*`, `woms-chat`, `chat-*`.
- **NEU-Bereiche:** Regeln zwischen den Kommentaren `NEU-ANFANG` und `NEU-ENDE` in `global.css` (Basis, Hilfsklassen und Bausteine aus Task 2 und 3) sind das neue System; ihre Benutzer entstehen in P1 Task 5 bis 8 und in P2 bis P9. Eine Klasse mit einer Regel dort gilt nie als tot und steht in `neuOhneBenutzer` (mit `--mit-neu` zählt sie normal, so findet P10 unbenutzt gebliebene neue Klassen). Ohne diese Trennung stünden 77 neue Klassen als „tot“ in der Liste.
- **Laufzeit-Klassen** (gelten immer als benutzt): `u-*` (uPlot erzeugt `u-legend` im DOM) und `mine` (Modifikator der Sprechblasen des Chat-Bausteins aus dem Kundenportal, gesetzt über `renderMessage(…, { mine })` in `ChatThread.jsx`).
## Ergebnis
| Lauf | definiert | tot |
|---|---|---|
| Vor P1 (`bd84d22`, ohne NEU-Bereiche) | 514 | **67** |
| Vor diesem Task (`a6877aa`), roh | 594 | 144 |
| davon Laufzeit-Klassen (`mine`, `u-legend`) | | 2 |
| davon NEU-Bereiche (Task 2 und 3, noch ohne Benutzer) | | 77 |
| **Kandidaten (Altlast)** | | **65** |
| Nach diesem Task | 524 | 3 (siehe Gegenprobe) |
Erwartet waren etwa 66 Namen (Grenzen 40 und 90). Der Lauf auf dem Stand vor P1 ergibt 67, die Altlast-Kandidaten im aktuellen Stand 65: Die Differenz sind `link-danger`, `num`, `nowrap`, `small`, `strong` und `xs` (jetzt NEU) sowie `mine` und `u-legend` (Laufzeit), dazu kommen `stat-label` und `stat-value` wie unten.
## Gegenprobe
Für jeden der 65 Kandidaten: `rg -w <name> src --glob '!*.css'`. Bei Treffern zusätzlich mit genauer Wortgrenze (`rg -e '(^|[^A-Za-z0-9_-])<name>($|[^A-Za-z0-9_-])' --glob '!*.css' src`), weil `-w` den Bindestrich als Wortgrenze behandelt und `stat-value` in `fin-stat-value` „findet“.
- 62 Kandidaten: 0 Treffer.
- `stat-label`, `stat-value`: je 3 Treffer, alle in `fin-stat-label` und `fin-stat-value` (`AboCard.jsx`); mit genauer Wortgrenze 0, also tot.
- `success`: 71 Treffer, wirklich benutzt: `AdminPage.jsx` Zeile 25 setzt `admin-notice ${notice.type}` (`success` oder `error`). Bleibt.
- `admin-status`, `admin-empty-line`: 0 Treffer, tot, aber in `pages/AdminPage.css`, das nicht zu diesem Paket gehört (Task 9 und P8 fassen die Datei an). Bleiben vorerst.
Gelöscht sind damit 62 Namen aus `global.css`.
| Klasse | Definition (Stand `a6877aa`) | `rg -w` Treffer | Ergebnis |
|---|---|---|---|
| `admin-empty-line` | pages/AdminPage.css:361, pages/AdminPage.css:372 | 0 | tot, bleibt vorerst: Datei liegt ausserhalb dieses Pakets (P8 baut die Admin-Seite um) |
| `admin-status` | pages/AdminPage.css:280, pages/AdminPage.css:293 | 0 | tot, bleibt vorerst: Datei liegt ausserhalb dieses Pakets (P8 baut die Admin-Seite um) |
| `bg-amber` | global.css:494 | 0 | gelöscht |
| `bg-black` | global.css:487 | 0 | gelöscht |
| `bg-blue` | global.css:490 | 0 | gelöscht |
| `bg-blue-grey` | global.css:491 | 0 | gelöscht |
| `bg-dark-grey` | global.css:484, global.css:748, global.css:761 | 0 | gelöscht |
| `bg-green` | global.css:488 | 0 | gelöscht |
| `bg-light-grey` | global.css:485 | 0 | gelöscht |
| `bg-orange` | global.css:495 | 0 | gelöscht |
| `bg-teal` | global.css:489 | 0 | gelöscht |
| `bg-white` | global.css:486 | 0 | gelöscht |
| `bg-yellow` | global.css:493 | 0 | gelöscht |
| `btn-secondary` | global.css:593 | 0 | gelöscht |
| `col-12` | global.css:695 | 0 | gelöscht |
| `container` | global.css:504 | 0 | gelöscht |
| `control-panel-section` | global.css:1009, global.css:1014 | 0 | gelöscht |
| `dashboard-section` | global.css:1238 | 0 | gelöscht |
| `drop-zone` | global.css:963, global.css:972 | 0 | gelöscht |
| `fade-in` | global.css:1041 | 0 | gelöscht |
| `flex-col` | global.css:1321 | 0 | gelöscht |
| `form-select` | global.css:638, global.css:654, global.css:1308, global.css:1309 | 0 | gelöscht |
| `gap-1` | global.css:1324 | 0 | gelöscht |
| `gap-4` | global.css:1324 | 0 | gelöscht |
| `mb-1` | global.css:707 | 0 | gelöscht |
| `modal-content` | global.css:904 | 0 | gelöscht |
| `modern-card` | global.css:993, global.css:1003, global.css:1306, global.css:1307 | 0 | gelöscht |
| `mt-1` | global.css:704 | 0 | gelöscht |
| `nav-link` | global.css:545, global.css:553 | 0 | gelöscht |
| `nav-right` | global.css:558 | 0 | gelöscht |
| `navbar` | global.css:518 | 0 | gelöscht |
| `navbar-brand` | global.css:531 | 0 | gelöscht |
| `navbar-nav` | global.css:540, global.css:1095 | 0 | gelöscht |
| `priority-high` | global.css:838 | 0 | gelöscht |
| `priority-low` | global.css:836 | 0 | gelöscht |
| `priority-medium` | global.css:837 | 0 | gelöscht |
| `priority-none` | global.css:835 | 0 | gelöscht |
| `quick-actions` | global.css:1239 | 0 | gelöscht |
| `side-group-label` | global.css:1134, global.css:1138 | 0 | gelöscht |
| `slide-in` | global.css:1045 | 0 | gelöscht |
| `slider` | global.css:916, global.css:926, global.css:930, global.css:941, global.css:946, global.css:957 | 0 | gelöscht |
| `slider-container` | global.css:911 | 0 | gelöscht |
| `spacer` | global.css:609 | 0 | gelöscht |
| `stat-card` | global.css:1234 | 0 | gelöscht |
| `stat-info` | global.css:1235 | 0 | gelöscht |
| `stat-label` | global.css:1237 | 3, alle nur Teil von `fin-stat-label` | gelöscht |
| `stat-value` | global.css:1236 | 3, alle nur Teil von `fin-stat-value` | gelöscht |
| `stats-grid` | global.css:1233 | 0 | gelöscht |
| `success` | pages/AdminPage.css:99 | 71 | bleibt: dynamischer Modifikator `admin-notice ${notice.type}` (AdminPage.jsx:25, Typen `success`/`error`) |
| `table-hover` | global.css:600, global.css:604 | 0 | gelöscht |
| `text-black` | global.css:498 | 0 | gelöscht |
| `text-green` | global.css:500 | 0 | gelöscht |
| `text-large` | global.css:701 | 0 | gelöscht |
| `text-right` | global.css:699 | 0 | gelöscht |
| `text-small` | global.css:700 | 0 | gelöscht |
| `text-xlarge` | global.css:702 | 0 | gelöscht |
| `ticket-collapsed` | global.css:742, global.css:748 | 0 | gelöscht |
| `ticket-expanded` | global.css:755, global.css:761 | 0 | gelöscht |
| `ticket-id` | global.css:742, global.css:755, global.css:794, global.css:807 | 0 | gelöscht |
| `ticket-id-expanded` | global.css:807, global.css:811 | 0 | gelöscht |
| `ticket-info` | global.css:820 | 0 | gelöscht |
| `ticket-row` | global.css:715, global.css:720, global.css:727, global.css:731, global.css:735 | 0 | gelöscht |
| `ticket-time` | global.css:815 | 0 | gelöscht |
| `worksheet-cell` | global.css:771, global.css:777 | 0 | gelöscht |
| `worksheet-expansion` | global.css:767 | 0 | gelöscht |
## Nicht gelöscht, obwohl der Abgleich sie roh nennt
- **NEU (77, Task 2 und 3, Benutzer folgen):** `actions`, `brand`, `btn-block`, `btn-primary`, `btn-quiet`, `cell-sub`, `cell-title`, `check`, `code-input`, `count`, `critical`, `crumb`, `dialog`, `dialog-body`, `dialog-close`, `dialog-foot`, `dialog-head`, `dialog-wide`, `err`, `eyebrow`, `fact`, `facts`, `field-row`, `field-row-3`, `hide-md`, `hide-sm`, `hint`, `hot`, `input`, `is-invalid`, `k`, `kv`, `link-danger`, `loading`, `login`, `login-box`, `login-foot`, `login-links`, `main`, `menu-open`, `mobile-bar`, `msg`, `nav`, `nav-count`, `nav-dot`, `nav-item`, `neg`, `nowrap`, `num`, `page-head`, `paused`, `prose`, `right`, `scrim`, `search`, `section`, `select`, `select-quiet`, `shell`, `sidebar`, `sidebar-foot`, `small`, `stat`, `stats`, `strong`, `sub`, `sw`, `tab`, `table`, `table-foot`, `tabs`, `textarea`, `toast`, `two-col`, `v`, `who`, `xs`
- **Laufzeit (2):** `mine`, `u-legend`.
- **Nur durch die Ausnahmeliste am Leben** (Lauf mit `--ohne-ausnahmen`, 6 Namen): `chat-bubble`, `chat-input-row`, `chat-meta` (Sprechblasen und Eingabezeile kommen zur Laufzeit aus dem Chat-Baustein des Portals), `ui-btn-danger`, `ui-btn-sm` (`Button.jsx` setzt sie über die Tabelle `VARIANTS` und `size`; beide stehen in KOMPAT). `status-occupied` hat keinen Benutzer: `statusClass()` in `lib/ticketStatus.js` liefert nur `status-open`, `-working`, `-awaiting`, `-assigned` und `-closed`. Es ist gelöscht, die übrigen fünf stehen in KOMPAT.
## Zusätzlich gelöscht (nicht tot, laut Plan)
- Alle `!important` in `global.css`: 77 vorher, 0 nachher. Offen bleiben 6 in `pages/AdminPage.css` und 2 in `admin-areas.css` (beide Dateien gehören nicht zu diesem Paket; Task 9 und P8).
- `.btn-green, .btn-primary { … !important }` samt Hover und `.btn-secondary`; `.btn-dark`, `.btn-teal`, `.btn-green` stehen neu in KOMPAT.
- `.footer` (zweimal), `.navbar*`, `.nav-link`, `.nav-right`, `.ticket-row*`, `.ticket-collapsed`, `.ticket-expanded`, `.ticket-id*`, `.ticket-time`, `.ticket-info`, `.worksheet-expansion`, `.worksheet-cell`, `.slider*`, `.drop-zone`, `.modern-card`.
- Die Legacy-Responsive-Regeln (`@media (max-width: 768px)` mit `.col-6`, `.navbar-nav`, `.ticket-tab`); `.col-6 { width: 100% }` unter 768 px steht neu in KOMPAT.
- Die Keyframes `slideDown`, `fadeIn` und `slideIn` (`WorksheetList.jsx` bringt eigene in einem `<style>`-Block mit).
- `.kpi*` einschließlich `.kpi-grid`: `Kpi` rendert ab Task 6 `Stat`; bis dahin hat `Kpi` kein Styling. `.fin-stack .kpi-grid` und `.fin-head .kpi-value` im Finanz-Teil bleiben (Task 9).
- `.priority-none`, `-low`, `-medium`, `-high` (tot); `.priority-critical` und `.status-*` stehen in KOMPAT.
- `.ui-card*`, `.ui-btn*`, `.badge-*`, `.form-group`, `.form-label`, `.form-control` (und `.form-select`, tot), `.card*`, `.row`, `.col`, `.col-6`, `.dropdown*`, `.spinner`, `.overlay-content`, `.ticket-tab*` und die Regeln „Soften legacy“: durch KOMPAT-Zeilen ersetzt.
- `.fin-form-field .form-control[aria-invalid="true"] { border-color: … !important }`: ersetzt durch die roten Ringe aus Task 3 (`.form-control[aria-invalid="true"]`).
- Die Aliasvariable `--sidebar-w-collapsed` (Task 2).
- Die toten Hilfsklassen `.flex-col`, `.gap-1`, `.gap-4`, `.container`, `.mt-1`, `.mb-1`, `.text-right`, `.text-small`, `.text-large`, `.text-xlarge`, `.col-12`, `.bg-*` außer `.bg-red`, `.text-black`, `.text-green` (stehen in der Tabelle oben).
### Abweichung: `.side*` bleibt bis Task 5
Der Plan nennt `.side*` zum Löschen. Das alte Menü (`Navbar.jsx`) benutzt sie aber bis Task 5. Ohne sie fällt das Alt-Gerüst `.app-shell` zusammen: Der Lauf `shots.sh --gate p1` brach auf fast allen Ansichten mit `step-failed` ab, weil das ungebremste Menü den Inhalt verdrängte (auf dem Handy auch ohne die Regel für `.collapsed`). Die Regeln stehen deshalb unverändert in einem eigenen Teil des KOMPAT-Blocks („Altes Menü“). Geändert sind nur `--sidebar-w-collapsed` (jetzt `72px`) und das tote `.side-group-label` (entfernt). **Task 5 löscht diesen Teil**; dafür muss `global.css` in die Dateiliste von Task 5. `.side-count` und `.side-logo` stehen wie im Plan als eigene KOMPAT-Zeilen.
## KOMPAT-Zeilen (Selektor → Look)
| Selektor | Look |
|---|---|
| `.ui-card`, `.ui-card.pad`, `.ui-card-head`, `.ui-card-head h3`, `.ui-card-body` | Abschnitt ohne Kasten, Kopf wie `h2.section` (16/24, 600) |
| `.ui-btn`, `.ui-btn-primary`, `.ui-btn-danger` | wie `.btn` (weiß, Ring, 32 px), `.btn-primary` (schwarz), `.link-danger` (roter Textlink); `-ghost` und `-sm` ohne eigene Regel (wirken wie `.ui-btn`) |
| `.btn-dark`, `.btn-teal`, `.btn-green` | wie `.btn-primary` |
| `.form-group`, `.form-label`, `.form-control` (mit `textarea`, `select`, `:focus`, `[readonly]`, Platzhalter) | wie `.field`, Beschriftung 500, `.input`/`.select`/`.textarea` |
| `.badge-muted`, `-info`, `-warn`, `-ok`, `-danger` | Badge-Töne grau, blau, orange, grün, rot |
| `.status-open`, `-working`, `-awaiting`, `-assigned`, `-closed`, `.priority-critical` | grau, blau, orange, violett, „plain“ (geschlossen und storniert teilen sich die Klasse), rot |
| `.overlay:has(> .overlay-content)` | `overflow-y: auto`, `align-items: flex-start`, Abstand 24/16, Hintergrund `--overlay`, ohne Einblend-Animation |
| `.overlay-content` (und `.overlay-content.fin-dialog`) | weiße Box: 100 %, max. 520 px (760 px bei `.fin-dialog`), Radius 12, `--shadow-pop`, Abstand 24, Schrift `--text` |
| `.overlay-close` | fest oben rechts, 32×32, weißer runder Knopf, „ד in `--text-2` |
| `.seg button.active` | wie `.seg button.on` |
| `.bg-red` (mit `.text-white` oder ohne), `.text-grey`, `.text-red` | kein Hintergrund, `--red-fg`, 13 px; `--text-2`; `--red-fg`. `.text-white` allein hat keine Regel |
| `.side-count`, `.side-logo` | kleine graue Zahl; Initiale ohne Verlauf |
| `.card`, `.card-header`, `.card-body` | neutral: ohne Kasten, Kopf mit Haarlinie |
| `div.row`, `.col`, `.col-6` (unter 768 px 100 %) | Raster ohne Look; `div.row`, damit `tr.row` unberührt bleibt |
| `.dropdown`, `.dropdown-content`, `.dropdown-item` | weißes Popover mit `--shadow-pop` |
| `.spinner` | grauer Ring |
| `.page .page`, `.page .main-content` | Abstand und Breite aufgehoben |
| `.fin-link`, `.legal-files a`, `.credit-cols a`, `.area-rows a` | feiner Unterstrich (`--line-strong`, beim Überfahren `currentColor`) |
| `.ticket-tabs`, `.ticket-tab`, `.ticket-tab-active`, `.ticket-tab-content` | wie `.tabs` und `.tab` (Unterstrich statt Kasten); nicht in der Spec-Tabelle, aber `Tabs.jsx` bleibt bis Task 6 |
| `.side*` („Altes Menü“) | siehe Abweichung oben; Task 5 löscht den Teil |
Das `:has()` der Alt-Dialoge ist nötig, damit `^\.overlay *\{` genau einmal trifft (Grundregel des neuen `.overlay > .dialog`).
Die Einblend-Animation des neuen `.overlay` (`fade`, Deckkraft 0 → 1) ließ `submitReachable` mit „kein offener Dialog gefunden“ scheitern, wenn der Dialog im ersten Bild geprüft wurde (`checkVisibility` mit `checkOpacity`). Alt-Dialoge hatten nie eine Animation, deshalb `animation: none` in KOMPAT. Task 7 muss dasselbe für `Dialog` lösen, z. B. mit `@media (prefers-reduced-motion: reduce) { .overlay, .dialog { animation: none; } }` (der Lauf setzt `reducedMotion: 'reduce'`).
Zusätzlich: `.datetime-pair input[type="date"]` bekommt `flex: 1.3`, weil das Datum im 520-px-Dialog sonst das Jahr abschneidet.
## Benutzt, aber ohne Regel (`undefinedUsed`)
Der Lauf nach diesem Task nennt 67 Namen. Der Lauf auf dem Stand vor P1 (`bd84d22`) nannte 58 davon schon; neu sind 9, alle gewollt:
| Name | Grund |
|---|---|
| `dot`, `empty-icon` | Task 3 hat die Regeln mit dem neuen `.badge` und `.empty` ersetzt; Task 6 baut `Badge` und `EmptyState` um (Prop `dot` und `icon` entfallen) |
| `footer` | Regel nach Plan gelöscht; Task 5 löscht `Footer.jsx` |
| `kpi`, `kpi-accent`, `kpi-label`, `kpi-sub` | Regeln nach Plan gelöscht; Task 6 lässt `Kpi` das neue `Stat` rendern |
| `text-white` | Spec: allein keine Regel |
| `ui-btn-ghost` | gleicher Look wie `.ui-btn`, braucht keine eigene Regel |
Die übrigen 58 hatten schon vor P1 keine Regel:
- die Bootstrap-Namen aus `WorksheetList.jsx` (wirkungslos): `alert`, `alert-info`, `align-items-center`, `border-0`, `col-md-6`, `d-flex`, `fs-5`, `fs-6`, `justify-content-between`, `justify-content-md-end`, `mb-3`, `mb-4`, `me-1`, `me-2`, `ms-2`, `ms-3`, `mx-2`, `overflow-hidden`, `p-3`, `px-3`, `py-2`, `py-3`, `rounded-3`, `shadow-sm`, `spinner-border`, `text-dark`, `text-md-end`, `text-muted`, `visually-hidden`, `timeline`, `timeline-item`;
- die Namen aus dem Plan: `p-4`, `btn-sm`, `status-filter`, `supporters-editor` (und `timeline` oben);
- reine Haken ohne Styling: `credits-view`, `fin-abo-amount`, `fin-abo-main`, `fin-abo-next`, `fin-abo-product`, `fin-assign`, `fin-days`, `fin-inv-abo`, `fin-memo`, `fin-tax-card`, `fin-tax-note`, `fin-todo-main`, `fin-total-value`, `fin-tx-out`, `fin-wrap`, `legal-doc`, `offer-form`, `sp-ampel`, `sp-apps`, `sp-cleanup-body`, `status-filter-actions`, `worksheet-list`, `worksheet-supporters`.
## Bewusst offen (P1 Task 9)
Stand nach Task 9: `node "$W/farben.mjs" "$WT"` meldet **0 Treffer** in allen vier Gründen (`kontrast<3`, `dunkel-auf-akzent`, `weiss`, `verlauf`). Vorher waren es 41, 1, 18 und 11 (zusammen 71). Was danach noch an Farbliteralen im Quelltext steht, ist hier mit Zielpaket und gemessenem Kontrast festgehalten; keine Zeile liegt unter 3:1.
| Stelle | Was | Kontrast (gemessen) | Zielpaket |
|---|---|---|---|
| `global.css:269` (`.select`) | Pfeil als zwei `linear-gradient` mit hartem Stopp (`transparent 50%`), Wert aus dem Prototyp; `farben.mjs` führt ihn als „ausgenommen“ (`--ohne-ausnahmen` zeigt ihn) | `--text-2` auf Weiß 5,76:1 | bleibt (Spec) |
| `global.css:1057` (KOMPAT, `select.form-control`) | derselbe Pfeil für das Altfeld | 5,76:1 | P10 (KOMPAT entfällt, `FormField` und die Altfelder gehen in P7 auf `.select`) |
| `global.css:669` `.legal-version-current` (Tönung `rgba(16,185,129,.08)`), `:680` `.legal-tone-red td` (`rgba(239,68,68,.10)`), `:682` `.legal-tone-orange td` (`rgba(245,158,11,.10)`) | Alpha-Tönungen als Zeilenhintergrund | Text `--text` auf den Tönungen 14,3 bis 15,1:1; `.legal-deadline` rot (`--danger`) 5,43:1, orange (`--orange-fg`) 6,43:1 | P8 (Rechtliches, Tönungen werden Badge-Töne) |
| `global.css:964` `.fin-confirm` (Tönung `rgba(245,158,11,.1)`) | Alpha-Tönung als Kastenhintergrund | Text `--text` auf der Tönung 15,1:1 | P7 (Finanzen) |
| `src/components/serverpuls/chartColors.js` | Canvas-Farben, die einzige erlaubte Ausnahme im Quelltext (Spec) | Linie 18,88:1, Achse 5,76:1, Spitze 3,10:1; Raster und Teilstriche sind Hilfslinien ohne Text | bleibt (Spec) |
| `src/components/Logo.jsx` (`fill="#111"`, `fill="#fff"`) | SVG-Marke mit zwei festen Farben | Weiß auf Schwarz 18,88:1 | bleibt (Marke) |
Außerhalb des Scans (er prüft nur CSS und JSX) und **nicht** unter „bewusst offen“, weil der Kontrast unter 3:1 liegt:
- `src/lib/previewEnv.js`, `DOT_COLORS` und `dotColor()`: Status-Punkte mit den Altfarben `#10b981` (2,54:1), `#f59e0b` (2,15:1), `#ef4444` (3,76:1) und `#a0aec0` (2,26:1) auf Weiß. Die Punkte sind höchstens 10 × 10 px groß und stehen immer neben dem Statustext (`ServicesPage`, `PreviewBlock`); der Browser-Check `dark-surface` ignoriert seit diesem Task Elemente bis 12 × 12 px. `tests/unit/previewEnv.test.mjs` hält die Hex-Werte fest, deshalb bleibt die Datei hier unverändert. Zielpaket: P5 (Projekte und Services): `dotColor()` liefert `var(--green-fg)`, `var(--orange-fg)`, `var(--red-fg)`, `var(--text-3)`, dazu der Test.
Nicht prüfbar im Werkzeug: Der Eingabebaustein des Chats kommt aus dem Kundenportal und ist dort eine leere Datei (`loadChatComposer`). Die Variablen `--chat-*` und die Meldungsfarben (`.chat-composer-note`, `.chat-file-size`, `.chat-audio-time`) sind laut Vorgabe gesetzt; ihr Laufzeitbild muss nach dem Livegang einmal im Chat angesehen werden (Zielpaket P6).