Zuletzt aktualisiert:

UI/UX-Audit Silverscale – 06.06.2026

Ziel: Vom „Prototyp" zum „fertigen Produkt" – die letzten 20 % Polish, damit die App Jaqueline im Alltag präsentiert werden kann (Wife Acceptance Factor, WAF). Methodik: statische Code-Analyse (3 parallele Audits: Konsistenz · Gaps/TODOs · UX-Best-Practices) plus Playwright-Sweep über alle 16 Routen (mobil 390×844 hell/dunkel, Desktop 1280×900, interaktive Flows). Screenshots: app/ui-audit/shots/ (34 Stück), Skript: app/ui-audit/shoot.py (wiederverwendbar für Regressions-Checks).


1. Gesamteindruck

Die App ist deutlich näher am „fertigen Produkt" als am Prototyp. Das maritime Design-System trägt, Dark Mode ist flächendeckend (nur Einzelfehler), alle 16 Routen rendern fehlerfrei, Empty-States existieren fast überall, das zentrale Fehler-Toast-System (lib/api.js) greift, Charts/Sparklines/Donut sind liebevoll. Keine einzige TODO/FIXME-Markierung im Code – die Codebase ist sauber gepflegt. Der Fitness-Strang (ADR-30–32) ist überraschend komplett.

Was fehlt, ist genau die gesuchte letzte Schicht: Vertrauens-Polish (Lösch-Schutz, Doppel-Submit, Eingabe-Validierung), Sprach-Polish (Bon-Rohnamen, Technik-Jargon) und Konsistenz-Polish (Spacing/Chips/ Buttons-Wildwuchs). Nichts davon ist architektonisch – alles ist in Abend-Sessions abarbeitbar.

Falsch-Alarm vorweg: Die Statistik-/Geräte-Charts wirkten in Full-Page- Screenshots leer – das ist ein Chart.js-Resize-Artefakt der Screenshot-Technik, im echten Viewport rendern sie korrekt (verifiziert).


2. Visuelle Befunde aus dem Screenshot-Sweep

Sortiert nach WAF-Relevanz (= „fällt Jaqueline sofort auf").

2.1 HOCH – Bon-Rohnamen verschmutzen die ganze App

Auf „Heute → Zuletzt gegessen" steht wörtlich: Coca-Cola light Coca-Cola light Mehrweg Pet 1L X11l (1,29 € / 1l) zzgl. Pfand 0,15 €. Dieselben Rohnamen stehen in Vorräten („ChickenNuggets o.Dip", „Apfel grün,säuerlich", „WassereisWass.melone", „Toilettenpap.4-lagig"), in der Artikel-Liste, im Tagebuch, in Statistik-Top-Produkten und in der Einkaufslisten-Historie („laktosefreie H Milch fettarm"). Das ist der größte einzelne Prototyp-Marker der App. Es gibt bereits foods.name als einzige Quelle – ein Batch-AI-Job „Anzeigename generieren" (analog ai-resolve) würde alle Oberflächen auf einmal heilen.

2.2 HOCH – Einstellungen sind eine Admin-Seite, keine Familien-Seite

einstellungen-m.png: Zwischen „Darstellung" und „Konto" stehen ein Refresh-Token-Textfeld (im Dark Mode als hartes weißes Rechteck – einziger echter Dark-Mode-Bruch), „Zur Klärung markiert" mit rohen EAN-Nummern (EAN 4335619058507 ohne OFF-Tr…), „🤖 Jetzt per Claude auflösen" und Gruppen-Sortier-Pfeile über 11 Gruppen. Für Jaqueline relevant sind nur: Hell/Dunkel, Benachrichtigungen, ihr Profil (Avatar/Budget), Abmelden. → Trennung in „Einstellungen" (alle) und „⚙️ Verwaltung" (Dennis, eigene Route oder Collapse) ist der schnellste WAF-Gewinn nach den Namen.

2.3 MITTEL – Vorräte: „Sonstiges (24)" ist die größte Gruppe

vorraete-m.png: Die Gruppierung läuft gut (Fleisch & Fisch, Getränke, Obst & Gemüse …), aber „Sonstiges" enthält 24 Artikel inkl. Non-Food (Blend-A-Med, Hundetrockennahrung, Pflaster, Toilettenpapier) zwischen Lebensmitteln. Dazu wiederholt sich das Label „ohne Nährwerte" zeilenweise als Rauschen. → Einmalige (AI-)Nachgruppierung + Non-Food ans Ende/eigener Abschnitt + „ohne Nährwerte" nur als dezentes Icon.

2.4 MITTEL – Titel-Truncation überall

Gerichte-Karten („BBQ Bowl mit Schv…", „Würziges Stir F…"), Tagebuch („CocaCola Zero Sugar – Null Zuc… "), Bon-Zeilen, Einkaufsliste. Auf 390 px wird fast jeder Titel abgeschnitten. → 2-zeilige Titel zulassen (-webkit-line-clamp: 2) kostet kaum Höhe und löst 80 % der Fälle; Rest löst sich durch 2.1 (kürzere Anzeigenamen).

2.5 MITTEL – Fitness-Detail-Chart: X-Achse ohne Jahr

geraet-viewport-check.png (Abduktor, Range „1J"): Achse zeigt 08.06. · 15.06. · 22.06. · 19.07. · 29.04. · 06.05. · 21.05. – chronologisch korrekt (2025→2026), liest sich aber als unsortiert, weil das Jahr fehlt. → Bei Ranges > 6 Monate Jahr ins Label (08.06.25) oder Monats-Ticks.

2.6 KLEIN – Einzelbefunde

2.7 Was visuell schon „fertig" ist ✨

Heute-Hero mit Ring + Makro-Balken (hell & dunkel top) · Tagebuch-Layout mit Mahlzeiten-Karten · Plan-Woche inkl. HF-Box-Karte und „eingeplant"-Chips · Einkaufsliste mit Gruppen + „Zuletzt verwendet" · FoodSheet-Suche (schnell, klare kcal/100g-Anzeige, drei Eingabewege) · Fitness-„Heute" mit Körperwert-Kacheln, Bio-Alter-Banner, „Frische Erfolge" · Besuchs-Detail mit Geräte-Fotos · Desktop-Sidebar-Layout wirkt erwachsen.


3. Statische Analyse A – Konsistenz (≈28 Befunde)

Design-Tokens & Farben

# Befund Wo Schwere
A1 Hartcodierte #fff/Hex-Inks statt Token (--ss-primary-ink existiert, wird nicht konsequent genutzt) Today.svelte:190/193, ShoppingList.svelte:175, Articles.svelte:120, Receipt.svelte:141 u. a. hoch
A2 4 verschiedene Backdrop-Farben/Blur für Modals (.45 vs .4 vs .7) App.svelte:245, Sheet.svelte:29, ScanResultPopup.svelte:62, ScanSheet.svelte:163 mittel
A3 Ad-hoc-Gradients statt --ss-grad-* Visit.svelte:97 (InBody-Lila), Today-Hero mittel
A4 Opacity-Werte (.3–.85) nicht theme-aware → Dark-Mode-Lesbarkeit DishCard.svelte:43, ShoppingList.svelte:182, Settings.svelte:309 mittel
A5 rgba(10,30,50,.75)-Overlays statt Token DishCard.svelte:44–46 niedrig

Komponenten-Extraktion (Copy-Paste-Patterns)

# Befund Wo Schwere
A6 6+ Chip-Varianten (chip2, mc, schip, hffilter, planchip …) mit je eigenem Padding/Radius Visit, Plan, Diary, Dishes, ShoppingList, Article hoch
A7 Delete-Button 4× lokal definiert (.del, .x …) Today:229, Diary:204, ShoppingList:131 mittel
A8 4 Empty-State-Implementierungen (Karte+Emoji vs. vs. Langtext) Dishes:80, Diary:99, Pantry:64, Visit:85 mittel
A9 2 Spinner-Typen, 5 Definitionen (.sonar-Keyframes 3× dupliziert) App, Article, Dish, Dishes, Settings niedrig
A10 15+ lokale Page-Header, Paddings streuen (14–16px) alle Seiten mittel
A11 Back-Button mal ‹ zurück, mal ‹ Einkäufe, Klasse .back nur teils definiert Article:136, Visit:34, Receipt:61 mittel
A12 „Mehr"-Menü nutzt eigenes Overlay statt Sheet.svelte; ScanResultPopup ebenso App.svelte:133–143 mittel

Microcopy & Formate

# Befund Wo Schwere
A13 „löschen" vs. „entfernen" vs. „Entfernen" gemischt Diary:96, ShoppingList:131, Dish:357, Article:259 mittel
A14 Toast-Schema uneinheitlich (Gespeichert ✓ vs ✓ Lieblingsartikel vs ohne ✓) – ~8 Varianten bei ~25 Aufrufen App-weit mittel
A15 Primär-Button-Labels: Speichern/Eintragen/Übernehmen/Verbrauchen ohne Regel FoodSheet:170, Dishes:131, Pantry:111 niedrig
A16 Datums-Ad-hoc-Slicing neben fmtDate() Plan:99, ShoppingList:84, Stats:68, Settings:198 mittel

Spacing/Typo-Skala

# Befund Zählung Schwere
A17 ~35 hartcodierte font-sizes (10/11/12/14/17/34/38px…) neben --ss-text-* App-weit hoch
A18 ~25 verschiedene Padding-Werte, keine Spacing-Skala (--ss-space-* fehlt) App-weit hoch
A19 8 Border-Radius-Werte neben --ss-r-* (5/6/7/8/10px…) App-weit mittel

Empfohlene Reihenfolge: Erst --ss-space-*-Skala + font-size-Mapping in app.css (1 h, mechanisch), dann Badge.svelte/EmptyState.svelte/ DeleteButton.svelte extrahieren (2–3 h), dann Farben/Backdrops (1 h).


4. Statische Analyse B – Gaps, TODOs, halbfertige Features

Code-Marker

Keine TODO/FIXME/HACK im gesamten Code. Offene Punkte leben ausschließlich in REPORT/ARCHITEKTUR (gute Disziplin).

Validierungs-Lücken (echte Bugs-in-Wartestellung)

# Befund Wo Fix
B1 DiaryIn.grams, ConsumeIn.qty, IngredientIn.grams ohne gt=0 → negative Gramm = negatives Budget möglich backend/main.py ~751/939/1059 Field(..., gt=0)
B2 ActivityIn.kcal ohne Grenzen → -100 kcal oder 99999 aufs Budget backend/main.py ~849 gt=0, lt=5000
B3 type="number" ohne min/inputmode im Frontend (9 Dateien) Pantry:99, Article:208–211, Activity:458 … min="0" inputmode="decimal/numeric"

Fehlerbehandlungs-Lücken

# Befund Wo
B4 Raw-fetch ohne Toast-Wrapper: Push-Subscribe (3×), ai-resolve-Status, FoodSheet-Foto/by-ean stores.js:86–109/128, FoodSheet.svelte:67–69
B5 Backend-Fehlertexte landen 1:1 im Toast („Fehler: food_id 12345 not found") api.js:16–19

Roadmap-Features – Code-Realität

Feature Status im Code
🤖 Woche füllen 0 % – kein Stub. Nur /api/plan-pick-Sortierung als Basis vorhanden
PVG-Packungsgewichte PVG fertig (Dish.svelte:266–307, main.py ~1816), aber pack-Modus schätzt pauschal „1 Packung"; foods.packaging_weight_g fehlt im Schema + UI-Feld
Mehrfach-Scan Listen-Abhaken 0 % (bewusst zurückgestellt)
Jaqueline-Login Heuristik in auth.py fertig; es fehlt nur der Pocket-ID-User + Erstlogin
iPhone-Verifikation Scan-Popup, Wir-Modus, Markdown, Donut – am Gerät ungetestet
Cross-User-Leak Fitness (ADR-30d) ✅ geprüft: user_id-Filter überall korrekt (visit/machine/machines + Sub-Queries)

Empty-/Lade-States (Rest-Lücken)


5. Statische Analyse C – UX-Best-Practices / Mobile

Vertrauen & Datensicherheit (WAF-kritisch!)

# Befund Wo Schwere
C1 window.confirm() beim Gericht-Löschen (OS-Dialog, bricht Design) Dish.svelte:177 hoch
C2 Tagebuch-/Listen-Löschen ohne Confirm und ohne Undo – ✕ tippen = weg Diary.svelte:49, Today.svelte:41 hoch
C3 Kein Doppel-Submit-Schutz: FoodSheet.save(), Diary.saveEdit(), Dish.remove() ohne saving-Lock → bei langsamem Netz Duplikat-Einträge FoodSheet.svelte:92–109 u. a. hoch
C4 Submit-Buttons ohne Spinner (disabled-Pattern existiert nur in Settings) FoodSheet:199, ActivitySheet:65 mittel

Touch & Eingabe

# Befund Wo
C5 ✕-Lösch-Buttons ~20×20px (iOS-Minimum 44px) Today:128, Diary:96, Pantry:146
C6 Stepper ±-Buttons 32px, eng Pantry:143
C7 Avatar-Switcher 38px, 6px Gap App.svelte:174
C8 inputmode fehlt auf mehreren Zahlenfeldern (iOS zeigt Volltastatur) Pantry:99, Article:208–211
C9 Enter-Submit inkonsistent (FoodSheet/Liste ja, Pantry/Article nein) div.
C10 Keine visualViewport-Behandlung: iOS-Tastatur kann Sheet-Felder/Toast verdecken Sheet.svelte:38, App.svelte:228

Daten-Frische & Feedback

# Befund Wo
C11 Kein visibilitychange-Refresh: App aus Hintergrund = Stale-Daten (gerade im Wir-Modus relevant, wenn beide loggen) App.svelte
C12 Optimistic Updates nur in ShoppingList.toggle(); Tagebuch/FoodSheet warten auf Server ShoppingList:51 vs. Diary:34

Accessibility

# Befund Wo
C13 Bottom-Nav/Mehr/Stepper ohne aria-label (Sheet/Diary haben es vorbildlich) App.svelte:69/126, Pantry:85
C14 Kein button:focus-visible-Stil (Inputs haben einen) app.css:146
C15 prefers-reduced-motion nirgends beachtet (fadeUp/sonar/spin/Puls) global
C16 .muted + 11px ≈ Kontrast 3,2:1 → WCAG-AA-Fail bei Kleintext app.css:164 + Pantry:79

PWA

# Befund Wo
C17 theme_color fix dunkel #0d2438 → falsche Statusbar-Farbe im Light Mode (Meta-Tag dynamisch per theme-Store setzen) manifest + index.html:7
C18 Offline = weiße Seite (bewusst kein SW-Cache, ADR-17 – aber ein navigator.onLine-Banner wäre billig) sw.js
C19 Safe-Area/dvh/Overscroll: ✅ alles korrekt gelöst
# Befund Wo
C20 Browser-Back schließt Sheets nicht (Hash-Router weiß nichts von Overlays); Mehr-Menü bleibt bei Hash-Wechsel offen (reproduziert) Sheet.svelte, App.svelte
C21 Scroll-Position bei Liste→Detail→Zurück geht verloren router.js
C22 Unbekannte Route = stiller Fallback auf Heute (ok, kein 404 nötig) App.svelte:114

Performance


6. 🏆 Top 10 Improvements: Prototyp → Produkt

Priorisiert nach WAF × Aufwand. „Abend" = heute machbar.

# Improvement Warum das den Unterschied macht Aufwand
1 Anzeigenamen-Politur: Batch-AI-Job generiert für alle ~1050 Artikel kurze deutsche Anzeigenamen („Coca-Cola light 1L" statt Bon-Zeile); Anzeige überall, Rohname bleibt als Tooltip/Feld Wirkt auf JEDER Seite gleichzeitig; der sichtbarste Prototyp-Marker verschwindet 1 Abend (Job + Spalte display_name + Frontend-Fallback)
2 Lösch-Vertrauen: einheitliches Confirm-Sheet (statt window.confirm) + Undo-Toast fürs Tagebuch + 44px-Touch-Targets für alle ✕ „Ich hab aus Versehen mein Frühstück gelöscht" ist der schnellste Vertrauenskiller 1–2 h
3 Submit-Härtung: saving-Lock + Spinner auf allen Save-Buttons (FoodSheet, ActivitySheet, Diary, Article, Settings) Verhindert Duplikat-Einträge bei Mobilfunk; sichtbares „es passiert was" 1–2 h
4 Einstellungen splitten: Familien-Teil (Darstellung, Push, Profil, Abmelden) vs. „Verwaltung"-Bereich (HF-Token, EAN-Klärung, Gruppen, AI) – plus „Claude"→„KI" im UI Die einzige Seite, die aktuell nach Bastelprojekt aussieht 1–2 h
5 Eingabe-Validierung beidseitig: Pydantic gt=0-Felder + min/inputmode auf allen Zahlenfeldern + Enter-Submit überall Schützt Budget-Integrität; Zahlen-Pad statt Volltastatur ist täglich spürbar 1 h
6 Frische-Garantie: visibilitychange-Refresh der aktiven Seite + Optimistic Toggle vereinheitlichen Wir-Modus: Jaqueline sieht sofort, was Dennis geloggt hat – sonst wirkt die App „kaputt" 1 h
7 Vorräte entrümpeln: AI-Nachgruppierung der 24 „Sonstiges", Non-Food-Sektion ans Ende, „ohne Nährwerte" → Icon, 2-zeilige Titel app-weit (line-clamp: 2) Vorräte ist die Browsing-Seite für den Küchenalltag 1 Abend
8 Design-System-Schulden: Spacing-/Type-Skala in app.css durchziehen, Badge/EmptyState/DeleteButton extrahieren, Backdrop-Token, weiße HF-Textarea im Dark fixen Macht alle künftigen Features automatisch konsistent; stoppt den Wildwuchs 1 Abend
9 A11y/Komfort-Paket: aria-labels (Nav, Stepper), :focus-visible, prefers-reduced-motion, Kontrast .muted-Kleintext, dynamische theme-color, Offline-Banner Die unsichtbaren 5 %, die „professionell" von „selbstgebaut" trennen 2 h
10 Fitness-Feinschliff + Jaqueline-Onboarding: Geräte-Chart-Labels mit Jahr, Empty-States Activity/Body, „+0 Aktivität"-Hero-Fix – und: Pocket-ID-User für Jaqueline anlegen + gemeinsamer Erstlogin als „Launch-Moment" 🚀 Der eigentliche Produkt-Launch: Sie loggt sich selbst ein, alles fühlt sich fertig an 1–2 h

Bewusst NICHT in den Top 10 (Roadmap, nicht Polish): 🤖 Woche füllen, PVG-Packungsgewichte, Mehrfach-Scan, Capacitor-iOS-App.


7. Abarbeitungsplan für heute Abend

Reihenfolge so gewählt, dass nach jedem Block deploybar ist:

Block 1 – Vertrauen (≈2 h): Top 2 + 3 + 5 → Confirm-Sheet-Komponente, Undo-Toast, 44px-Targets, saving-Locks, Pydantic-Validierung, inputmode/min.

Block 2 – Sprache (≈2 h): Top 1 + Teile von 4 → display_name-Spalte + AI-Batch-Job anstoßen (läuft im Hintergrund), Frontend auf display_name || name umstellen; Einstellungen-Split.

Block 3 – Wahrnehmung (≈1,5 h): Top 6 + 7 (line-clamp sofort, AI-Gruppen über Nacht) + Quick-Fixes aus §2.6 (Hero +0, ActivitySheet-CTA, weiße Textarea).

Block 4 – wenn noch Energie (≈1,5 h): Top 8/9 mechanische Teile (Spacing-Skala, aria-labels, focus-visible, reduced-motion, theme-color).

Danach: Frontend-Build + Deploy + gemeinsamer iPhone-Durchgang (erledigt nebenbei die offene iPhone-Verifikation aus REPORT §3) + Jaquelines Pocket-ID-User = Launch. 🎉


8. ✅ Abarbeitungs-Status (gleicher Abend, Blocks 1–4 umgesetzt)

Block Commit Inhalt Verifiziert
1 Vertrauen 7118095 Undo-Toast (DELETE→restore), ConfirmDialog statt window.confirm, saving-Locks+Spinner (6 Stellen), Pydantic gt-Grenzen, inputmode/min, 44px-Tap-Zonen, 422-Toasts lesbar Playwright: Undo-Roundtrip + Dialog-Screenshot; curl: 422 bei −50 g/−100 kcal
2 Sprache 38093b1 foods.display_name + COALESCE-Schicht (row2food + 6 Queries), polish_names.py (Haiku-Batch, alle ~1050 Artikel), Artikel-Seite mit Anzeigename/Bon-Name, Settings-Split (Verwaltung eingeklappt), Claude→KI, textarea.field Dark-Fix Screenshots Settings/Vorräte; Namen live („5 % Rinderhack", „Chicken Nuggets ohne Dip")
3 Wahrnehmung b7e5dde visibilitychange-Refresh (60 s Bump / 30 min Reload), .clamp2-Titel, Vorräte: Gruppen-Reihenfolge-Bugfix + Non-Food ans Ende, Tagebuch-„+ eintragen"-CTA (presetMeal), Geräte-Chart-Jahreslabels, Einkäufe-Filial-Dedupe Screenshots aller 5 Flächen
4 A11y/Tokens 74f21c3 Backdrop-Token, :focus-visible, prefers-reduced-motion, ink-3-Kontrast (WCAG), dynamische theme-color, Offline-Toast, aria-labels, Backfill-Endpoint + --groups-Pass Sweep ui-audit/shots-final/
Bonus b9020b3 eGYM-Sync-Fix: Netpulse meldet tote Sessions als 403 → Auth-Retry griff nie; jetzt 401+403 → Re-Login. Sync-Button pollt Status & zeigt Fehler als Toast Heutiger Besuch (06.06., 13 Geräte, InBody) synct & rendert

Doku: ADR-33 (display_name) + ADR-34 (UX-Härtung) in app/ARCHITEKTUR.md, CLAUDE.md-Stand aktualisiert. Offen aus den Top 10: Jaquelines Pocket-ID-Erstlogin (braucht euch beide + iPhone) und die iPhone-Verifikation der neuen Flows – Rest ist live auf https://silverscale.dennisfisch.de.

9. Anhang