clusev/docs/v1-ui-review.md

13 KiB
Raw Permalink Blame History

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-9min-h-11 min-w-11 (grid place-items-center behalten).
    • sidebar.blade.php:10 (Schließen) und :48 (Abmelden-Link <a>) — h-9 w-9min-h-11 min-w-11.
    • livewire/dashboard.blade.php:69 — „Hinzufügen"-Button min-h-9min-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:
    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-pingmotion-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-4bg-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.
  • 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).