# Clusev v1 — Konsolidierte Fix-Liste

Alle Punkte gegen die echten Dateien geprüft. Duplikate (Focus-States, Touch-Targets, KPI-Balken, Ringe, mobile CPU/RAM-Anzeige) sind zusammengeführt. Reine Bereitschafts-/Konventionshinweise ohne Datei-Defekt sind unter „Bewusst weggelassen" gelistet.

---

## MUST-FIX (Regelverstoß / echter Bug / A11y-Blocker)

### A11y — Tastatur-Fokus unsichtbar
- **Dateien:** `nav-item.blade.php`, `topbar.blade.php` (Z. 3, 16), `sidebar.blade.php` (Z. 10, 19, 48), `server-item.blade.php` (Z. 5), `livewire/dashboard.blade.php` (Z. 69)
- **Änderung:** Auf jeder interaktiven Basisklasse einen Token-Fokusring ergänzen: `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/60 focus-visible:ring-offset-2 focus-visible:ring-offset-surface`. Alternativ eine Base-Layer-Regel in `app.css`: `*:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px }`.
- **Grund:** Null Fokus-Styles im gesamten View-Layer (per grep bestätigt) — Tastaturbedienung auf der dunklen UI ist unmöglich.

### A11y — Kontrast `ink-3` unter WCAG AA
- **Datei:** `resources/css/app.css:36` (`--color-ink-3: #69757F`)
- **Änderung:** Auf ca. `#7E8A95` aufhellen (≈4,6:1 auf surface).
- **Grund:** 3,95:1 auf surface — `ink-3` ist Default für fast jeden Sekundärtext (IPs, Subtitles, Unit-Labels, systemd-Beschreibungen) bei `text-[11px]`/`text-xs`, also gilt 4,5:1.

### A11y — Kontrast `ink-4` unter WCAG AA (für Text)
- **Datei:** `resources/css/app.css:37` (`--color-ink-4: #495159`)
- **Änderung:** Für Textnutzung auf ca. `#707B85` (≈4,5:1) aufhellen, oder `ink-4` strikt auf Hairlines/Nicht-Text beschränken und betroffene Textstellen (`dashboard.blade.php:107,109`, `server-item.blade.php:13,19`) auf `text-ink-3` umstellen.
- **Grund:** 2,31:1 — fällt sogar unter AA-large (3:1), wird aber für echten Lesetext (Audit-Timestamps/Targets, CPU/RAM-Spaltenlabels) genutzt.

### A11y — Off-Canvas-Drawer nicht aus Tab-Order entfernt
- **Datei:** `resources/views/components/sidebar.blade.php:2` (`<aside>`)
- **Änderung:** Geschlossenen Zustand mit `x-bind:inert="!nav"` (plus `:aria-hidden`) binden, auf `lg` immer interaktiv halten; Fokus beim Öffnen auf den Schließen-Button setzen.
- **Grund:** Nur `-translate-x-full` schiebt den Drawer aus dem Bild — Tastatur/Screenreader tabben weiter in unsichtbare Off-Canvas-Links.

### R7 — Touch-Targets unter 44px
- **Dateien/Änderungen:**
  - `topbar.blade.php:3` und `:16` — Menü- und Glocken-Button `h-9 w-9` → `min-h-11 min-w-11` (grid `place-items-center` behalten).
  - `sidebar.blade.php:10` (Schließen) und `:48` (Abmelden-Link `<a>`) — `h-9 w-9` → `min-h-11 min-w-11`.
  - `livewire/dashboard.blade.php:69` — „Hinzufügen"-Button `min-h-9` → `min-h-11`.
- **Grund:** R7 fordert ≥44px; diese realen Tap-Ziele sind 36px (Switcher/Nav-Items nutzen bereits korrekt `min-h-11`).

### Bug — NaN-Guard greift an der falschen Stelle
- **Datei:** `resources/js/app.js:34-37` (`push()`)
- **Änderung:**
  ```js
  push(v) { const n = Number(v); if (!Number.isFinite(n)) return; this.last = n; this.points.push(n); if (this.points.length > this.max) this.points.shift(); }
  ```
- **Grund:** `Number(e.cpu)` bei fehlendem/falschem Key liefert `NaN`; die Klammer in `coords()` lässt `NaN` durch → `<polyline>` wird ungültig (Sparkline verschwindet) und der Zähler zeigt `NaN%`.

### Bug/Leak — Reverb-Channel wird nie abgebaut
- **Datei:** `resources/js/app.js:26-32` (`init()`)
- **Änderung:** Channel-Referenz halten und `destroy() { window.Echo?.leave('metrics'); }` zum Alpine-Objekt hinzufügen; `state_change`-Handler ebenfalls per Referenz unbinden.
- **Grund:** Kein `Echo.leave`/`unbind` vorhanden (grep bestätigt). Heute durch Full-Reload maskiert, aber sobald `wire:navigate` für die geplanten Server/Dienste/Dateien-Seiten kommt, stapeln sich Subscriptions auf dem Singleton `window.Echo`.

### R10 — KPI-Fortschrittsbalken ignoriert den Status-Triad
- **Datei:** `resources/views/components/kpi.blade.php:23`
- **Änderung:** Balkenfarbe aus `$tone` ableiten, z. B. `$barColor = ['ink'=>'bg-accent','online'=>'bg-online','warning'=>'bg-warning','offline'=>'bg-offline','accent'=>'bg-accent','cyan'=>'bg-cyan'][$tone] ?? 'bg-accent';` und `bg-accent` ersetzen. Width-Inline-Style bleibt (R4).
- **Grund:** Balken ist hart `bg-accent`; die `tone`-Prop färbt nur die Zahl — eine getonte KPI mit `pct` zeigt orangen Balken unter grüner/roter Zahl.

### Datenintegrität — Ressourcen-Ringe zeigen hartkodierte Konstanten
- **Datei:** `resources/views/livewire/dashboard.blade.php:57-60`
- **Änderung:** Ringe an echte Daten binden (mind. CPU/RAM aus denselben `$avgCpu`/`$avgMem` wie die KPIs) und Ton schwellenwertgesteuert in `x-ring` setzen (grün <75, amber 75–90, rot >90) statt fixem `tone` je Metrik.
- **Grund:** `:value="34/61/48"` mit statischen Tönen — „Disk" rendert amber bei 48% nur wegen `tone="warning"`, irreführend in einem Monitoring-View und entkoppelt vom Datenstrom.

### R7/UX — Keine Empty-/Loading-States in den Listen
- **Datei:** `resources/views/livewire/dashboard.blade.php` (Server `73-78`, systemd `82-93`, Audit `98-112`)
- **Änderung:** Jedes `@foreach` zu `@forelse … @empty` mit zentrierter, gedämpfter DE-Empty-Zeile (z. B. `<div class="px-5 py-8 text-center font-mono text-[11px] text-ink-3">Keine Server im Cluster</div>`); `wire:loading`-Skeleton für die per SSH/Queue hydrierten Panels.
- **Grund:** Bare `@foreach` ohne Fallback (grep: 0 forelse/@empty/wire:loading) — leere Daten rendern blanke Boxen.

---

## NICE-TO-HAVE (Polish)

### A11y — `prefers-reduced-motion` fehlt
- **Datei:** `resources/views/components/status-dot.blade.php:7`
- **Änderung:** `animate-ping` → `motion-safe:animate-ping`, oder Base-Layer-Regel in `app.css`, die `.animate-ping` bei reduzierter Bewegung deaktiviert.
- **Grund:** Dauerhafte Ping-Animation auf jedem Online-Dot ohne Reduced-Motion-Guard (grep: 0).

### A11y — `<nav>` ohne zugänglichen Namen
- **Datei:** `resources/views/components/sidebar.blade.php:31`
- **Änderung:** `<nav aria-label="Hauptnavigation" …>`.
- **Grund:** Generisches „navigation"-Landmark für Screenreader.

### A11y — Live-Chart Disconnected-Zustand kaum wahrnehmbar
- **Datei:** `resources/views/livewire/dashboard.blade.php:46`
- **Änderung:** Dot `aria-hidden="true"` (Text daneben trägt die Info) und Disconnected-Farbe `bg-ink-4` → `bg-warning`: `:class="connected ? 'bg-online' : 'bg-warning'"`.
- **Grund:** `bg-ink-4` (2,3:1) ist als Statusindikator praktisch unsichtbar.

### R7/Responsive — Per-Server CPU/RAM auf Mobile komplett ausgeblendet
- **Datei:** `resources/views/components/server-item.blade.php:11`
- **Änderung:** Kompakte Mono-Zeile nur `<sm` einblenden (z. B. `CPU 34% · RAM 61%`), ab `sm` auf die Balken umschalten.
- **Grund:** `hidden sm:flex` lässt unter 375px die wichtigste At-a-glance-Metrik ersatzlos verschwinden.

### R7 — Server-Item-Zeile ohne Mindesthöhe
- **Datei:** `resources/views/components/server-item.blade.php:5`
- **Änderung:** `min-h-11` zur `<a>`-Klasse ergänzen.
- **Grund:** Nur `py-2.5`, keine Mindesthöhe — in der einzeiligen `<sm`-Variante kann die Tap-Höhe unter 44px fallen.

### R10 — Drei verschiedene Gauge-Track-Tokens
- **Dateien:** `kpi.blade.php:22` (`bg-inset`), `server-item.blade.php:14,20` (`bg-line`), `ring.blade.php:16` (`text-line`)
- **Änderung:** Ein einheitliches Track-Treatment wählen (`bg-inset` liest als vertiefter Kanal am besten) für alle Progress-/Gauge-Hintergründe; Ring-Track ggf. mit kräftigerem `line-strong`.
- **Grund:** Gleicher „Gauge-Hintergrund"-Begriff, drei verschiedene Tokens — `bg-line` (rgba 0.10) ist als Track fast unsichtbar.

### Polish — Ring-Cap bei 0% und kein Übergang
- **Datei:** `resources/views/components/ring.blade.php:17-18`
- **Änderung:** `stroke-linecap="butt"` bzw. den Wert-Kreis per `@if($v > 0)` schützen, Track-Stroke verstärken, `transition-[stroke-dashoffset] duration-500` ergänzen.
- **Grund:** Bei Wert 0 malt `stroke-linecap="round"` einen Streu-Punkt; Live-Updates springen ohne Transition.

### Polish — Sparkline ohne Baseline/Marker, verzerrter Stroke
- **Datei:** `resources/views/livewire/dashboard.blade.php:36-45`
- **Änderung:** `vector-effect="non-scaling-stroke"` auf die Polylines (oder `preserveAspectRatio="none"` entfernen), dezente Baseline/Gridline via `border-line`, Accent-Punkt am letzten Wert.
- **Grund:** `preserveAspectRatio="none"` streckt den 1,5px-Stroke ungleichmäßig; kein Bezugsrahmen/aktueller Wert auf der Linie.

### Polish — Inkonsistentes Eyebrow-Tracking
- **Dateien:** `dashboard.blade.php:14` (`tracking-[0.2em]`), `kpi.blade.php:10` (`tracking-wider`), `sidebar.blade.php:32` (`tracking-widest`); `server-item.blade.php:13,19` (kein Tracking)
- **Änderung:** Alle uppercase-Mono-Mikrolabels auf einen Wert vereinheitlichen (`tracking-widest`), arbitrary `tracking-[0.2em]` ersetzen; ggf. `x-eyebrow`-Helper.
- **Grund:** Gleiche Rolle (Mono-Eyebrow), vier verschiedene Letter-Spacings.

### Polish — Notifications-Dot kollidiert mit Brand-Accent
- **Datei:** `resources/views/components/topbar.blade.php:20`
- **Änderung:** Unread-Dot von `bg-accent` (orange) auf `bg-cyan` oder `bg-warning`.
- **Grund:** Orange liest als „aktiv/Brand", nicht als „Aufmerksamkeit"; Token-Intent für Accent wahren.

### API/R11 — `server-item` und Abmelden-Link hart `href="#"`
- **Dateien:** `server-item.blade.php:5`, `sidebar.blade.php:48`
- **Änderung:** `$href`-Prop ergänzen (`@props([… 'href' => '#'])`, `<a href="{{ $href }}">`), später `:href="route('servers.show', $s['id'])"` übergeben.
- **Grund:** Server-Liste nicht navigierbar und kein Slot für den von R11 geforderten UUID-Route-Key.

### API — `kpi`-`tone` themt Widget nur halb
- **Datei:** `resources/views/components/kpi.blade.php:22-24`
- **Änderung:** Identisch zum Must-Fix oben (Balkenfarbe aus `$tone`). Hier separat als API-Konsistenz notiert.
- **Grund:** `tone` impliziert das ganze Widget, färbt aber nur die Zahl.

### Datenform — Mock divergiert vom MetricsTicked/SSH-Kontrakt
- **Datei:** `app/Livewire/Dashboard.php:28-50`
- **Änderung:** Jedem Server eine `id` (UUID) und ein verschachteltes `metrics => ['cpu','mem','disk','series']` geben, damit Panel und Ringe aus `$servers[$active]` lesen und `MetricsTicked.server` 1:1 auf eine Zeile mappt.
- **Grund:** Globale `$metrics`-Serie ohne Server-Key und Rows ohne stabile id überleben den Broadcast/SSH-Kontrakt nicht.

### Security-Readiness — öffentlicher `metrics`-Channel
- **Datei:** `resources/js/app.js:20` (+ `app/Events/MetricsTicked.php`)
- **Änderung:** TODO setzen; mit Auth auf `PrivateChannel('fleet.metrics')` + `routes/channels.php`-Gate migrieren, in `app.js` auf `window.Echo.private(...)` umstellen.
- **Grund:** Öffentlicher Channel (kein `routes/channels.php`) — jeder Browser kann Flotten-CPU abonnieren; akzeptabel in der Mock-Phase, vor echten Daten zu flaggen.

### R9-Klarheit — englische systemd-Beschreibungen im Mock
- **Datei:** `app/Livewire/Dashboard.php:36-40`
- **Änderung:** Entweder Code-Kommentar „spiegelt reales Unit-`Description=`" (zulässig unter §6) oder bis zur echten SSH-Schicht auf knappe deutsche Beschreibungen umstellen.
- **Grund:** Handgeschriebene Mock-Copy rendert als sichtbarer Sekundärtext (`dashboard.blade.php:88`) und kann als englische UI-Copy gelesen werden.

### R3/R6-Sauberkeit — totes `welcome.blade.php`
- **Datei:** `resources/views/welcome.blade.php`
- **Änderung:** Datei löschen.
- **Grund:** Einzige Quelle roher Hex-Farben in `resources/views`, nicht geroutet/referenziert — verschmutzt jedes R3-Audit.

### Defensive Formatierung — `x-text="last + '%'"`
- **Datei:** `resources/views/livewire/dashboard.blade.php:51`
- **Änderung:** `x-text="Math.round(last) + '%'"` (nach dem `push()`-Guard).
- **Grund:** Verhindert lange Dezimalstellen aus dem künftigen SSH-Poller; sichtbare Oberfläche des NaN-Bugs oben.

### Optional — Progressbar-Semantik
- **Dateien:** `kpi.blade.php:22`, `server-item.blade.php` (Balken)
- **Änderung:** `role="progressbar" aria-valuenow="{{ $pct }}" aria-valuemin="0" aria-valuemax="100"` am Balken-Wrapper.
- **Grund:** Server-Item-Balken haben sichtbar keinen zugeordneten Zahlenwert; niedrige Prio, da KPI die Zahl textlich zeigt.

---

## Bewusst weggelassen (kein Datei-Defekt)
- **R1–R11 Architektur:** Keine echten Verstöße gefunden (Routing, Livewire-3-Klassen, Token-only Farben, German Copy, Component-Kit alle verifiziert).
- **`config/livewire.php` fehlt:** Reiner Explizitheits-/Bootstrap-Hinweis — die Livewire-3-Defaults matchen die §4-Konvention; kein funktionaler Defekt.
- **`server-item` aria-valuenow / KPI-Progress-Semantik:** Als optional behalten, nicht als Blocker (siehe oben).