clusev/docs/v1-ui-review.md

163 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 7590, 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)
- **R1R11 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).