fix(dashboard): align reader dashboard with reader-dashboard-final spec #483

Closed
opened 2026-05-08 16:15:43 +02:00 by marcel · 9 comments
Owner

Kontext

Das Reader-Dashboard wurde in 2be2087a (feat(dashboard): wire reader layout to +page.svelte) verdrahtet und seitdem durch a11y/test-Commits geschliffen, weicht aber substanziell von der finalen Spec docs/specs/reader-dashboard-final.html ab (Merge B.1 + B.3, „Final · Bereit zur Umsetzung"). Die Spec wurde nie tatsächlich umgesetzt — das aktuelle Layout ist näher an einer generischen Chip-Reihe als an dem dort beschriebenen weißen Header-Balken / Grid-basierten Lesedashboard.

Dieses Issue bündelt alle identifizierten Diffs zwischen Spec und +page.svelte + frontend/src/lib/shared/dashboard/Reader*.svelte für einen einzigen Refactor-Schritt.

Quellen

  • Spec: docs/specs/reader-dashboard-final.html
    • Sektion 1: Desktop READ_ALL
    • Sektion 2: BLOG_WRITE-Variante (Zone 3 „Meine Entwürfe")
    • Sektion 3: Dark Theme
    • Sektion 4: Mobile (3 Varianten, Breakpoint sm = 640 px)
    • Impl-Ref-Tabelle: ab Z. 1104 (Werte aus dieser Tabelle gelten — die skalierten Mockup-Pixelwerte gelten nicht)
  • Aktueller Stand: frontend/src/routes/+page.svelte ({#if data.isReader}-Branch) + Reader{StatsStrip,PersonChips,DraftsModule,RecentDocs,RecentStories}.svelte

Acceptance Criteria

A. Layout / Reihenfolge

  • Greeting + Stats werden ein einziger weißer Header-Balken mit vertikalem 1 px-Divider: bg-white border border-line rounded-sm px-4 py-3 flex items-center gap-4. Das eigenständige <h1 class="font-serif text-[2rem]"> aus +page.svelte entfällt.
  • Reader-Reihenfolge READ_ALL: Header-Bar → Person-Grid → Content-Row.
  • Reader-Reihenfolge READ_ALL + BLOG_WRITE: Header-Bar → Drafts-Card → Person-Grid → Content-Row.
  • Content-Row ist grid grid-cols-1 sm:grid-cols-2 gap-1.5 (1 : 1) — nicht flex-[3] / flex-[2].
  • Inhalts-Wrapper: max-w-7xl mx-auto px-8 py-8 (kein responsives px-4 sm:px-6 lg:px-8).

B. Header-Balken

  • Greeting-Block (links): kleines Tageszeit-Label in eigener Zeile (text-[11px] font-bold uppercase tracking-[.8px] text-[#C8B8A0]) über font-serif text-xl text-brand-navy „Herzlich willkommen, {name}.".
  • 3 Stats inline (rechts): jede Spalte mit border-r border-[#F0EDE6] (außer letzte). Zahl text-2xl font-black text-brand-navy leading-none block. Label darunter text-[11px] font-bold uppercase tracking-[.8px] text-ink-3 block mt-0.5.
  • Vertikaler Divider zwischen Greeting und Stats: w-px self-stretch bg-line flex-shrink-0.
  • Stats sind keine eigenständigen Karten mehr (kein border border-line bg-surface shadow-sm pro Stat, kein hover:border-brand-mint).
  • Mobile (< 640 px): Header-Balken stapelt — Greeting-Block oben, Stats-Block darunter mit border-t pt-1.5. Stat-Labels nutzen Kurzform „Dok.", „Pers.", „Gesch.".
  • Neue i18n-Schlüssel (de/en/es), gemäß Spec Z. 1086 ff.:
    • dashboard.greeting.morning/afternoon/evening (nur Tageszeit, ohne Name)
    • dashboard.welcome parametrisch („Herzlich willkommen, {name}.")
    • dashboard.stat.documents/persons/stories mit mobiler Kurzform-Variante
    • dashboard.recentlyUpdated, dashboard.myDrafts, dashboard.persons.viewAll
  • Bestehende Schlüssel greeting_morning/day/evening (kombiniert mit Name) werden migriert oder gelöscht.

C. Personen-Grid

  • grid grid-cols-2 sm:grid-cols-4 gap-1.5, Karten sind quadratisch und vertikal gestapelt (flex flex-col items-center text-center gap-1.5).
  • Kachel: bg-surface border border-line rounded-sm p-2.5 no-underline. Kein border-l-3 border-l-brand-mint, kein Bottom-Akzent, kein hover:border-brand-mint.
  • Avatar Desktop: w-9 h-9 rounded-full shadow-sm flex items-center justify-center font-black text-white text-[11px] flex-shrink-0 (aktuell 32 px ohne Schatten).
  • Avatar Mobile (< sm): w-[22px] h-[22px], kleinere Schrift.
  • Document-Count als Mint-Pill: text-[11px] font-bold text-brand-navy bg-[#D4F0EE] px-1.5 py-px rounded-full (statt plain text-ink-3).
  • Komponenten-Heading <h2>{m.dashboard_reader_person_chips_heading()}</h2> entfällt — der Block startet direkt mit dem Grid (Spec hat keinen Heading).
  • „Alle N Personen →" rechts unter dem Grid: text-xs font-semibold text-[#4A6E8A] no-underline text-right block mt-1. Keine underline, kein text-brand-navy.

D. Recent-Docs Card („Zuletzt aktualisiert")

  • Card-Pattern: bg-surface border border-line rounded-sm overflow-hidden flex flex-col. Kein p-6, kein shadow-sm.
  • Card-Head: flex items-center justify-between px-3 py-1.5 border-b border-line. h3 in text-[11px] font-bold uppercase tracking-[.12em] text-ink-3. Link „Alle Dokumente" rechts: text-[11px] font-semibold text-brand-navy/40 no-underline.
  • Doc-Row: flex items-center gap-2 px-3 py-1.5 border-b border-line/50 last:border-b-0. Bestandteile: Thumb (links) → Titel + Sender-Zeile (mitte, flex-1) → Datum (rechts).
  • Thumb-Spalte: w-5 h-6 bg-[#ECEAE4] border border-line rounded-[2px] flex items-center justify-center flex-shrink-0. Doc-Icon für Dokumente, Foto-Icon (Mint-Stroke) für Image-Dokumente — Spec Z. 446 ff.
  • Sender-Zeile: text-xs text-ink-3 mit Präfix „von "; bei fehlendem Sender wird Em-Dash „—" in text-line angezeigt (nicht ausblenden).
  • NEW / UPDATED-Badges entfallen vollständig (in Spec nicht enthalten — Spec arbeitet rein über Sortierung nach updatedAt).
  • Komponenten-<h2> entfällt — Heading sitzt ausschließlich im Card-Head.

E. Geschichten Card

  • Card- und Card-Head-Konvention identisch zu Recent-Docs (siehe D). „Alle Geschichten" als Card-Head-Link rechts — nicht als Block-Link unter der Liste.
  • Story-Row: px-3 py-2 border-b border-line/50 last:border-b-0. Aufbau vertikal: Titel → Excerpt → Meta.
  • Story-Title: font-serif text-base italic text-ink.
  • Excerpt: text-xs text-ink-2 leading-relaxed line-clamp-2 (Token ink-2, nicht ink-3).
  • Meta: text-[11px] text-ink-3.
  • {#if stories.length > 0} Wrapper bleibt (Card wird bei leerer Liste ausgeblendet).

F. Drafts-Card (BLOG_WRITE)

  • Mint Left-Border: border-l-[3px] border-l-brand-mint zusätzlich zu border border-line.
  • Card-Head wie Recent-Docs (kompakt, mit border-b), kein „Alle"-Link, h3 = „Meine Entwürfe".
  • Draft-Row: flex items-center justify-between px-3 py-1.5 border-b border-line/50 last:border-b-0.
    • Linker Block: Titel font-serif text-sm text-brand-navy + Meta text-[11px] text-ink-3 mit Format „Entwurf · zuletzt bearbeitet {relative}".
    • Rechter Block: Chevron-Icon (<svg width=7 height=7>).
  • Komponenten-<h2> entfällt.

G. Dark-Mode

  • Alle Karten / Header verwenden dark:bg-surface dark:border-white/8 (über die existierenden Tailwind-dark:-Token — nicht die !important-Override-Klassen aus der Spec).
  • Stat-Label und Card-Head h3: dark:text-ink-4 (#6070A0, AA-konform).
  • Datum / Meta: dark:text-ink-5 (#5A6888).
  • Mint-Pill im Dark-Mode: dark:bg-brand-mint/15 dark:text-brand-mint.
  • Sender, Excerpt: dark:text-ink-3 (#7080A8).

H. Tests / NFRs

  • Bestehende Vitest-Specs der Reader*-Komponenten werden auf die neue Markup-Struktur angepasst (Person-Card statt Chip, Card-Head-Link statt Footer-Link, Mint-Pill-Selektor, Header-Bar statt separate Stat-Karten, kein NEW/UPDATED-Badge).
  • axe-Playwright auf / (Reader-Variante, Light + Dark) bleibt grün. Geprüfte Kontraste:
    • text-ink-3 #706C68 ≥ 4.5:1 auf bg-canvas
    • „Alle N Personen →" #4A6E8A ≥ 4.5:1 auf bg-canvas
    • Mint-Pill #002850 auf #D4F0EE ≥ 4.5:1
  • Visual Regression Snapshots bei 320 / 768 / 1440 px für Reader Light + Dark + BLOG_WRITE — neue Baselines werden eingecheckt.
  • Touch-Target ≥ 44 px bleibt für alle klickbaren Elemente erhalten (Person-Karte gesamtklickbar, Stat-Spalte, Doc-Row, Draft-Row, „Alle …"-Link).

I. Out of scope

  • Keine Änderung am +page.server.ts-Loader, keine neuen DTOs, keine Backend-Änderungen.
  • Keine Änderung am Author-Branch ({:else} in +page.svelte) — das betrifft nur den Reader.
  • Person-eigene Avatar-Farbe (Spec zeigt unterschiedliche Hex-Töne pro Beispielperson). Aktuelle Hash-Palette aus d554fc7e bleibt erhalten — bei Bedarf eigenes Folge-Issue.
  • ADR-007 (Reader-Diskriminante data.isReader) bleibt unverändert gültig.

Hinweis

Die in der Spec gelegentlich genannten text-ink-2 / text-ink-3 / text-ink-4 / text-ink-5-Token müssen ggf. in layout.css ergänzt werden, falls sie noch nicht existieren — Token-Abgleich vor der Implementierung prüfen, sonst entstehen unaufgelöste Tailwind-Klassen.

## Kontext Das Reader-Dashboard wurde in `2be2087a` (`feat(dashboard): wire reader layout to +page.svelte`) verdrahtet und seitdem durch a11y/test-Commits geschliffen, weicht aber substanziell von der finalen Spec `docs/specs/reader-dashboard-final.html` ab (Merge B.1 + B.3, „Final · Bereit zur Umsetzung"). Die Spec wurde nie tatsächlich umgesetzt — das aktuelle Layout ist näher an einer generischen Chip-Reihe als an dem dort beschriebenen weißen Header-Balken / Grid-basierten Lesedashboard. Dieses Issue bündelt **alle** identifizierten Diffs zwischen Spec und `+page.svelte` + `frontend/src/lib/shared/dashboard/Reader*.svelte` für einen einzigen Refactor-Schritt. ## Quellen - Spec: `docs/specs/reader-dashboard-final.html` - Sektion 1: Desktop READ_ALL - Sektion 2: BLOG_WRITE-Variante (Zone 3 „Meine Entwürfe") - Sektion 3: Dark Theme - Sektion 4: Mobile (3 Varianten, Breakpoint `sm` = 640 px) - Impl-Ref-Tabelle: ab Z. 1104 (Werte aus dieser Tabelle gelten — die skalierten Mockup-Pixelwerte gelten **nicht**) - Aktueller Stand: `frontend/src/routes/+page.svelte` (`{#if data.isReader}`-Branch) + `Reader{StatsStrip,PersonChips,DraftsModule,RecentDocs,RecentStories}.svelte` ## Acceptance Criteria ### A. Layout / Reihenfolge - [ ] Greeting + Stats werden ein **einziger weißer Header-Balken** mit vertikalem 1 px-Divider: `bg-white border border-line rounded-sm px-4 py-3 flex items-center gap-4`. Das eigenständige `<h1 class="font-serif text-[2rem]">` aus `+page.svelte` entfällt. - [ ] Reader-Reihenfolge READ_ALL: Header-Bar → Person-Grid → Content-Row. - [ ] Reader-Reihenfolge READ_ALL + BLOG_WRITE: Header-Bar → **Drafts-Card** → Person-Grid → Content-Row. - [ ] Content-Row ist `grid grid-cols-1 sm:grid-cols-2 gap-1.5` (1 : 1) — **nicht** `flex-[3] / flex-[2]`. - [ ] Inhalts-Wrapper: `max-w-7xl mx-auto px-8 py-8` (kein responsives `px-4 sm:px-6 lg:px-8`). ### B. Header-Balken - [ ] Greeting-Block (links): kleines Tageszeit-Label in eigener Zeile (`text-[11px] font-bold uppercase tracking-[.8px] text-[#C8B8A0]`) **über** `font-serif text-xl text-brand-navy` „Herzlich willkommen, {name}.". - [ ] 3 Stats inline (rechts): jede Spalte mit `border-r border-[#F0EDE6]` (außer letzte). Zahl `text-2xl font-black text-brand-navy leading-none block`. Label darunter `text-[11px] font-bold uppercase tracking-[.8px] text-ink-3 block mt-0.5`. - [ ] Vertikaler Divider zwischen Greeting und Stats: `w-px self-stretch bg-line flex-shrink-0`. - [ ] Stats sind **keine** eigenständigen Karten mehr (kein `border border-line bg-surface shadow-sm` pro Stat, kein `hover:border-brand-mint`). - [ ] Mobile (< 640 px): Header-Balken stapelt — Greeting-Block oben, Stats-Block darunter mit `border-t pt-1.5`. Stat-Labels nutzen Kurzform „Dok.", „Pers.", „Gesch.". - [ ] Neue i18n-Schlüssel (de/en/es), gemäß Spec Z. 1086 ff.: - `dashboard.greeting.morning/afternoon/evening` (nur Tageszeit, **ohne** Name) - `dashboard.welcome` parametrisch („Herzlich willkommen, {name}.") - `dashboard.stat.documents/persons/stories` mit mobiler Kurzform-Variante - `dashboard.recentlyUpdated`, `dashboard.myDrafts`, `dashboard.persons.viewAll` - [ ] Bestehende Schlüssel `greeting_morning/day/evening` (kombiniert mit Name) werden migriert oder gelöscht. ### C. Personen-Grid - [ ] `grid grid-cols-2 sm:grid-cols-4 gap-1.5`, Karten sind quadratisch und vertikal gestapelt (`flex flex-col items-center text-center gap-1.5`). - [ ] Kachel: `bg-surface border border-line rounded-sm p-2.5 no-underline`. **Kein** `border-l-3 border-l-brand-mint`, **kein** Bottom-Akzent, **kein** `hover:border-brand-mint`. - [ ] Avatar Desktop: `w-9 h-9 rounded-full shadow-sm flex items-center justify-center font-black text-white text-[11px] flex-shrink-0` (aktuell 32 px ohne Schatten). - [ ] Avatar Mobile (< sm): `w-[22px] h-[22px]`, kleinere Schrift. - [ ] Document-Count als **Mint-Pill**: `text-[11px] font-bold text-brand-navy bg-[#D4F0EE] px-1.5 py-px rounded-full` (statt plain `text-ink-3`). - [ ] Komponenten-Heading `<h2>{m.dashboard_reader_person_chips_heading()}</h2>` entfällt — der Block startet direkt mit dem Grid (Spec hat keinen Heading). - [ ] „Alle N Personen →" rechts unter dem Grid: `text-xs font-semibold text-[#4A6E8A] no-underline text-right block mt-1`. **Keine** `underline`, **kein** `text-brand-navy`. ### D. Recent-Docs Card („Zuletzt aktualisiert") - [ ] Card-Pattern: `bg-surface border border-line rounded-sm overflow-hidden flex flex-col`. **Kein** `p-6`, **kein** `shadow-sm`. - [ ] Card-Head: `flex items-center justify-between px-3 py-1.5 border-b border-line`. h3 in `text-[11px] font-bold uppercase tracking-[.12em] text-ink-3`. Link „Alle Dokumente" rechts: `text-[11px] font-semibold text-brand-navy/40 no-underline`. - [ ] Doc-Row: `flex items-center gap-2 px-3 py-1.5 border-b border-line/50 last:border-b-0`. Bestandteile: Thumb (links) → Titel + Sender-Zeile (mitte, flex-1) → Datum (rechts). - [ ] Thumb-Spalte: `w-5 h-6 bg-[#ECEAE4] border border-line rounded-[2px] flex items-center justify-center flex-shrink-0`. Doc-Icon für Dokumente, Foto-Icon (Mint-Stroke) für Image-Dokumente — Spec Z. 446 ff. - [ ] Sender-Zeile: `text-xs text-ink-3` mit Präfix „von "; bei fehlendem Sender wird Em-Dash „—" in `text-line` angezeigt (nicht ausblenden). - [ ] **NEW / UPDATED-Badges entfallen vollständig** (in Spec nicht enthalten — Spec arbeitet rein über Sortierung nach `updatedAt`). - [ ] Komponenten-`<h2>` entfällt — Heading sitzt ausschließlich im Card-Head. ### E. Geschichten Card - [ ] Card- und Card-Head-Konvention identisch zu Recent-Docs (siehe D). „Alle Geschichten" als Card-Head-Link rechts — **nicht** als Block-Link unter der Liste. - [ ] Story-Row: `px-3 py-2 border-b border-line/50 last:border-b-0`. Aufbau vertikal: Titel → Excerpt → Meta. - [ ] Story-Title: `font-serif text-base italic text-ink`. - [ ] Excerpt: `text-xs text-ink-2 leading-relaxed line-clamp-2` (Token `ink-2`, **nicht** `ink-3`). - [ ] Meta: `text-[11px] text-ink-3`. - [ ] `{#if stories.length > 0}` Wrapper bleibt (Card wird bei leerer Liste ausgeblendet). ### F. Drafts-Card (BLOG_WRITE) - [ ] Mint Left-Border: `border-l-[3px] border-l-brand-mint` zusätzlich zu `border border-line`. - [ ] Card-Head wie Recent-Docs (kompakt, mit `border-b`), **kein** „Alle"-Link, h3 = „Meine Entwürfe". - [ ] Draft-Row: `flex items-center justify-between px-3 py-1.5 border-b border-line/50 last:border-b-0`. - Linker Block: Titel `font-serif text-sm text-brand-navy` + Meta `text-[11px] text-ink-3` mit Format „Entwurf · zuletzt bearbeitet {relative}". - Rechter Block: Chevron-Icon (`<svg width=7 height=7>`). - [ ] Komponenten-`<h2>` entfällt. ### G. Dark-Mode - [ ] Alle Karten / Header verwenden `dark:bg-surface dark:border-white/8` (über die existierenden Tailwind-`dark:`-Token — **nicht** die `!important`-Override-Klassen aus der Spec). - [ ] Stat-Label und Card-Head h3: `dark:text-ink-4` (`#6070A0`, AA-konform). - [ ] Datum / Meta: `dark:text-ink-5` (`#5A6888`). - [ ] Mint-Pill im Dark-Mode: `dark:bg-brand-mint/15 dark:text-brand-mint`. - [ ] Sender, Excerpt: `dark:text-ink-3` (`#7080A8`). ### H. Tests / NFRs - [ ] Bestehende Vitest-Specs der `Reader*`-Komponenten werden auf die neue Markup-Struktur angepasst (Person-Card statt Chip, Card-Head-Link statt Footer-Link, Mint-Pill-Selektor, Header-Bar statt separate Stat-Karten, kein NEW/UPDATED-Badge). - [ ] axe-Playwright auf `/` (Reader-Variante, Light + Dark) bleibt grün. Geprüfte Kontraste: - `text-ink-3 #706C68` ≥ 4.5:1 auf `bg-canvas` - „Alle N Personen →" `#4A6E8A` ≥ 4.5:1 auf `bg-canvas` - Mint-Pill `#002850` auf `#D4F0EE` ≥ 4.5:1 - [ ] Visual Regression Snapshots bei 320 / 768 / 1440 px für Reader Light + Dark + BLOG_WRITE — neue Baselines werden eingecheckt. - [ ] Touch-Target ≥ 44 px bleibt für alle klickbaren Elemente erhalten (Person-Karte gesamtklickbar, Stat-Spalte, Doc-Row, Draft-Row, „Alle …"-Link). ### I. Out of scope - Keine Änderung am `+page.server.ts`-Loader, keine neuen DTOs, keine Backend-Änderungen. - Keine Änderung am Author-Branch (`{:else}` in `+page.svelte`) — das betrifft nur den Reader. - Person-eigene Avatar-Farbe (Spec zeigt unterschiedliche Hex-Töne pro Beispielperson). Aktuelle Hash-Palette aus `d554fc7e` bleibt erhalten — bei Bedarf eigenes Folge-Issue. - ADR-007 (Reader-Diskriminante `data.isReader`) bleibt unverändert gültig. ## Hinweis Die in der Spec gelegentlich genannten `text-ink-2 / text-ink-3 / text-ink-4 / text-ink-5`-Token müssen ggf. in `layout.css` ergänzt werden, falls sie noch nicht existieren — Token-Abgleich vor der Implementierung prüfen, sonst entstehen unaufgelöste Tailwind-Klassen.
marcel added the P1-highbugui labels 2026-05-08 16:15:50 +02:00
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Observations

  • Section G referenziert dark:text-ink-4 (#6070A0) und dark:text-ink-5 (#5A6888). Diese Token existieren nicht in frontend/src/routes/layout.css — definiert sind nur --c-ink, --c-ink-2, --c-ink-3. Wird Section G so verdrahtet, fallen die Tailwind-Klassen still durch.
  • Section B/C/D/E inlinen Hex-Werte (#C8B8A0, #F0EDE6, #4A6E8A, #ECEAE4, #D4F0EE). Das umgeht die @theme inline-Disziplin in layout.css und macht die Cards für Dark-Mode-Wechsel unsichtbar.
  • 13 dashboard_reader_*-Keys existieren bereits (messages/{de,en,es}.json 449–464). Sektion B verlangt eine parallele neue Key-Familie (dashboard.greeting.*, dashboard.welcome, dashboard.stat.*). Ein paralleler Satz ist Doppelarbeit — die bestehenden Wordings stimmen größtenteils mit der Spec überein.
  • Die 5 Reader*.svelte.spec.ts (349 Zeilen) testen aktuell die Chip-Reihe + NEW/UPDATED-Badges + getrennte Stat-Karten. Section A/B/C/D macht jeden dieser Specs ungültig — kein „Tweak", sondern Rewrite.
  • NEW/UPDATED-Badges in ReaderRecentDocs.svelte wurden gerade erst poliert (5a8a1898 ISO-vs-numeric, f7eefb52 AA-Kontrast, 7bd477d2 Touch-Target). Section D verlangt deren ersatzlose Streichung — das ist eine bewusste Rücknahme von 3 frischen Commits.

Recommendations

  • Tokens-First-Schritt vor jeder Markup-Änderung: Sektion in layout.css ergänzen, in einem eigenen Commit:
    • --palette-mint-soft: #d4f0eebg-mint-soft (für die Pill).
    • --c-greeting-time: #c8b8a0 (light) / #4a5880 (dark) → text-greeting-time.
    • --c-line-soft: #f0ede6 (light) / rgba(255,255,255,.06) (dark) → border-line-soft.
    • --c-link-quiet: #4a6e8a (light) / var(--palette-mint) (dark) → text-link-quiet.
    • Falls Sektion G beibehalten wird: --c-ink-4, --c-ink-5 (light + dark) ergänzen, sonst Sektion G auf text-ink-2 / text-ink-3 reduzieren.
  • Bestehende i18n-Keys behalten und nur die Lücken füllen (dashboard_greeting_time_morning/afternoon/evening, dashboard_stat_documents_short etc.). Migration ist Reibung ohne Mehrwert.
  • TDD-Reihenfolge: pro Region Specs umbenennen → neue rote Specs anhand der Spec-Acceptance-Criteria schreiben → Markup grün ziehen. Vertikale Slices in dieser Reihenfolge: ReaderHeaderBar (neu) → ReaderPersonGrid (umbau aus PersonChips) → ReaderRecentDocsReaderRecentStoriesReaderDraftsModule.
  • Eine neue Komponente ReaderHeaderBar.svelte extrahieren — die Greeting+Stats-Bar braucht keine Logik in +page.svelte und ist isoliert testbar.
  • bg-white (statt bg-surface) für die Header-Bar mit einem Inline-Kommentar dokumentieren, der auf die Spec-Zeile B.1 zeigt — sonst nimmt der nächste Refactor das wieder raus.

Open Decisions

  • Existierende i18n-Keys ersetzen vs. ergänzen — Spec verlangt dashboard.greeting.morning (ohne Name), aktuell existiert greeting_morning (mit Name). Ersetzen heißt 13 weitere Keys + Spec-Compliance, Ergänzen heißt 3 neue Keys + ein Mix von zwei Konventionen.
## 👨‍💻 Felix Brandt — Senior Fullstack Developer ### Observations - Section G referenziert `dark:text-ink-4` (`#6070A0`) und `dark:text-ink-5` (`#5A6888`). Diese Token existieren **nicht** in `frontend/src/routes/layout.css` — definiert sind nur `--c-ink`, `--c-ink-2`, `--c-ink-3`. Wird Section G so verdrahtet, fallen die Tailwind-Klassen still durch. - Section B/C/D/E inlinen Hex-Werte (`#C8B8A0`, `#F0EDE6`, `#4A6E8A`, `#ECEAE4`, `#D4F0EE`). Das umgeht die `@theme inline`-Disziplin in `layout.css` und macht die Cards für Dark-Mode-Wechsel unsichtbar. - 13 `dashboard_reader_*`-Keys existieren bereits (`messages/{de,en,es}.json` 449–464). Sektion B verlangt eine parallele neue Key-Familie (`dashboard.greeting.*`, `dashboard.welcome`, `dashboard.stat.*`). Ein paralleler Satz ist Doppelarbeit — die bestehenden Wordings stimmen größtenteils mit der Spec überein. - Die 5 `Reader*.svelte.spec.ts` (349 Zeilen) testen aktuell die Chip-Reihe + NEW/UPDATED-Badges + getrennte Stat-Karten. Section A/B/C/D macht jeden dieser Specs ungültig — kein „Tweak", sondern Rewrite. - NEW/UPDATED-Badges in `ReaderRecentDocs.svelte` wurden gerade erst poliert (`5a8a1898` ISO-vs-numeric, `f7eefb52` AA-Kontrast, `7bd477d2` Touch-Target). Section D verlangt deren ersatzlose Streichung — das ist eine bewusste Rücknahme von 3 frischen Commits. ### Recommendations - **Tokens-First-Schritt vor jeder Markup-Änderung**: Sektion in `layout.css` ergänzen, in einem eigenen Commit: - `--palette-mint-soft: #d4f0ee` → `bg-mint-soft` (für die Pill). - `--c-greeting-time: #c8b8a0` (light) / `#4a5880` (dark) → `text-greeting-time`. - `--c-line-soft: #f0ede6` (light) / `rgba(255,255,255,.06)` (dark) → `border-line-soft`. - `--c-link-quiet: #4a6e8a` (light) / `var(--palette-mint)` (dark) → `text-link-quiet`. - Falls Sektion G beibehalten wird: `--c-ink-4`, `--c-ink-5` (light + dark) ergänzen, sonst Sektion G auf `text-ink-2` / `text-ink-3` reduzieren. - Bestehende i18n-Keys behalten und nur die Lücken füllen (`dashboard_greeting_time_morning/afternoon/evening`, `dashboard_stat_documents_short` etc.). Migration ist Reibung ohne Mehrwert. - TDD-Reihenfolge: pro Region Specs umbenennen → neue rote Specs anhand der Spec-Acceptance-Criteria schreiben → Markup grün ziehen. Vertikale Slices in dieser Reihenfolge: `ReaderHeaderBar` (neu) → `ReaderPersonGrid` (umbau aus PersonChips) → `ReaderRecentDocs` → `ReaderRecentStories` → `ReaderDraftsModule`. - Eine neue Komponente `ReaderHeaderBar.svelte` extrahieren — die Greeting+Stats-Bar braucht keine Logik in `+page.svelte` und ist isoliert testbar. - `bg-white` (statt `bg-surface`) für die Header-Bar mit einem Inline-Kommentar dokumentieren, der auf die Spec-Zeile B.1 zeigt — sonst nimmt der nächste Refactor das wieder raus. ### Open Decisions - Existierende i18n-Keys ersetzen vs. ergänzen — Spec verlangt `dashboard.greeting.morning` (ohne Name), aktuell existiert `greeting_morning` (mit Name). Ersetzen heißt 13 weitere Keys + Spec-Compliance, Ergänzen heißt 3 neue Keys + ein Mix von zwei Konventionen.
Author
Owner

🏛️ Markus Keller — Application Architect

Observations

  • Reiner Frontend-Refactor. Backend, DTOs, +page.server.ts, Loader bleiben unangetastet — Section I bestätigt das. Keine Modul-Boundary-Implikationen.
  • ADR-007 (Reader-Diskriminante data.isReader) bleibt 1:1 gültig — kein neues ADR erforderlich.
  • Doc-Update-Tabelle: dieser Change berührt keine Migration, keinen neuen Controller/Service, keinen neuen Route, keine neue Permission/ErrorCode/Domain-Term. Es entfällt also jede Diagramm- oder Glossar-Pflege.
  • Section G hat einen architektonischen Geruch: Token-Slots werden im Issue-Body angesprochen, ohne dass die Token-Datei (layout.css) erweitert wird. Tokens sind Architektur-Kontrakt — sie gehören in @theme inline, nicht in den Komponenten als arbiträre Hex-Werte.
  • bg-white statt bg-surface ist eine bewusste Spec-Entscheidung (B.1, Spec Z. 1131). Das ist keine Ebenen-Verletzung, aber es bricht das im Projekt etablierte Card-Pattern aus CLAUDE.md (bg-surface). Eine Inline-Kommentar-Notiz reicht — kein ADR.

Recommendations

  • Issue-Body um eine Implementierungs-Klausel erweitern: „Alle in Sektionen B–G genannten Hex-Werte werden zuerst als CSS-Custom-Properties in frontend/src/routes/layout.css (@theme inline) registriert. Komponentenmarkup verwendet ausschließlich Tailwind-Token-Klassen, keine bg-[#…]-Arbitrary-Values."
  • Section G konkretisieren: entweder --c-ink-4 / --c-ink-5 real in layout.css ergänzen (mit AA-Kontrast-Kommentar wie für --c-ink-3 schon vorhanden), oder die Sektion auf existierende text-ink-2 / text-ink-3 reduzieren. Kein Issue darf undefined Tailwind-Klassen vorschreiben.
  • Keine Komponentenduplikation: ReaderRecentDocs und ReaderRecentStories haben jetzt nahezu identisches Card-Head-Markup. Lieber nicht in eine Generic-Card-Komponente extrahieren (KISS) — Duplikation auf zwei Stellen ist günstiger als eine erzwungene Abstraktion.
  • ADR ist hier nicht nötig — es entstehen keine langfristigen architektonischen Konsequenzen, nur visuelle.
## 🏛️ Markus Keller — Application Architect ### Observations - Reiner Frontend-Refactor. Backend, DTOs, `+page.server.ts`, Loader bleiben unangetastet — Section I bestätigt das. Keine Modul-Boundary-Implikationen. - ADR-007 (Reader-Diskriminante `data.isReader`) bleibt 1:1 gültig — kein neues ADR erforderlich. - Doc-Update-Tabelle: dieser Change berührt **keine** Migration, **keinen** neuen Controller/Service, **keinen** neuen Route, **keine** neue Permission/ErrorCode/Domain-Term. Es entfällt also jede Diagramm- oder Glossar-Pflege. - Section G hat einen architektonischen Geruch: Token-Slots werden im Issue-Body angesprochen, ohne dass die Token-Datei (`layout.css`) erweitert wird. Tokens sind Architektur-Kontrakt — sie gehören in `@theme inline`, nicht in den Komponenten als arbiträre Hex-Werte. - `bg-white` statt `bg-surface` ist eine bewusste Spec-Entscheidung (B.1, Spec Z. 1131). Das ist keine Ebenen-Verletzung, aber es bricht das im Projekt etablierte Card-Pattern aus `CLAUDE.md` (`bg-surface`). Eine Inline-Kommentar-Notiz reicht — kein ADR. ### Recommendations - Issue-Body um eine Implementierungs-Klausel erweitern: „Alle in Sektionen B–G genannten Hex-Werte werden zuerst als CSS-Custom-Properties in `frontend/src/routes/layout.css` (`@theme inline`) registriert. Komponentenmarkup verwendet ausschließlich Tailwind-Token-Klassen, keine `bg-[#…]`-Arbitrary-Values." - Section G konkretisieren: entweder `--c-ink-4` / `--c-ink-5` real in `layout.css` ergänzen (mit AA-Kontrast-Kommentar wie für `--c-ink-3` schon vorhanden), oder die Sektion auf existierende `text-ink-2` / `text-ink-3` reduzieren. Kein Issue darf undefined Tailwind-Klassen vorschreiben. - Keine Komponentenduplikation: `ReaderRecentDocs` und `ReaderRecentStories` haben jetzt nahezu identisches Card-Head-Markup. Lieber **nicht** in eine Generic-Card-Komponente extrahieren (KISS) — Duplikation auf zwei Stellen ist günstiger als eine erzwungene Abstraktion. - ADR ist hier nicht nötig — es entstehen keine langfristigen architektonischen Konsequenzen, nur visuelle.
Author
Owner

🛡️ Nora "NullX" Steiner — Application Security Engineer

Observations

  • Reiner UI-Refactor: keine neuen Endpunkte, keine Auth-Flow-Änderung, keine Datenexposition, keine externen Links, keine User-Input-Verarbeitung, kein Datei-Upload. OWASP Top 10 ist hier nicht angerissen.
  • +page.server.ts und der Reader-Loader-Pfad bleiben laut Section I unverändert — Permission-Boundary (READ_ALL ohne WRITE_ALL/ANNOTATE_ALL) wird durch ADR-007 erzwungen, das bleibt valid.
  • Keine target="_blank"-Links in Spec — rel="noopener noreferrer" nicht relevant.
  • Der einzige potenzielle Smell ist die Hex-Streuung in Sektionen B–G: Tokens sind auditierbar, Hex-Literale streuen ungeprüft. Das ist Code-Quality, nicht Security.

Recommendations

  • Keine Security-Action erforderlich. Implementation darf ohne Security-Review mergen.
  • Falls die Implementation versehentlich <a href={…}> mit user-controlled Daten einführt (z. B. personAvatarColor als inline-Style), npm run lint deckt nichts davon ab — kurzes Manual-Review der Templates reicht.
## 🛡️ Nora "NullX" Steiner — Application Security Engineer ### Observations - Reiner UI-Refactor: keine neuen Endpunkte, keine Auth-Flow-Änderung, keine Datenexposition, keine externen Links, keine User-Input-Verarbeitung, kein Datei-Upload. OWASP Top 10 ist hier nicht angerissen. - `+page.server.ts` und der Reader-Loader-Pfad bleiben laut Section I unverändert — Permission-Boundary (`READ_ALL` ohne `WRITE_ALL`/`ANNOTATE_ALL`) wird durch ADR-007 erzwungen, das bleibt valid. - Keine `target="_blank"`-Links in Spec — `rel="noopener noreferrer"` nicht relevant. - Der einzige potenzielle Smell ist die Hex-Streuung in Sektionen B–G: Tokens sind auditierbar, Hex-Literale streuen ungeprüft. Das ist Code-Quality, nicht Security. ### Recommendations - Keine Security-Action erforderlich. Implementation darf ohne Security-Review mergen. - Falls die Implementation versehentlich `<a href={…}>` mit user-controlled Daten einführt (z. B. `personAvatarColor` als inline-Style), `npm run lint` deckt nichts davon ab — kurzes Manual-Review der Templates reicht.
Author
Owner

🧪 Sara Holt — QA Engineer & Test Strategist

Observations

  • 5 Component-Specs (Reader*.svelte.spec.ts, 349 Zeilen) testen die aktuelle Struktur: Chip-Reihe (ReaderPersonChips), getrennte Stat-Karten (ReaderStatsStrip), NEW/UPDATED-Badges (ReaderRecentDocs), padded Cards mit <h2>. Sektionen A–F machen alle 5 Specs ungültig — Rewrite, nicht Patch.
  • +page.svelte hat eine Reader-Branch-Spec (page.svelte.spec.ts, commit 64bcc8d0) die das aktuelle Layout assertiert. Auch zu rewriten.
  • Section H listet axe-Playwright auf / Reader light + dark + BLOG_WRITE. Aktuell existiert keine Reader-spezifische Playwright-Spec — das ist neue Test-Infrastruktur, kein Update. CI-Budget: ~1–2 min zusätzlich.
  • Visual-Regression bei 320 / 768 / 1440 px × {light, dark, BLOG_WRITE} = 9 neue Baselines, nicht aktualisierte. Die Issue-Wording „neue Baselines werden eingecheckt" verschleiert, dass die alten gelöscht werden müssen.
  • Touch-Target-Konflikt: Doc-Row px-3 py-1.5 und Draft-Row px-3 py-1.5 ergeben Zeilenhöhen ≈ 24–28 px. Memory feedback_a11y und project_user_split_transcribe_vs_read sagen: Read-Path ist Critical auf Mobile, 44 px Touch-Target Pflicht. Die Spec-Werte verletzen das. Section H listet 44 px als Acceptance, Section D listet die Padding-Werte daneben — das passt nicht zusammen.
  • NEW/UPDATED-Badges in ReaderRecentDocs.svelte wurden mit eigenen Tests in 5a8a1898 (fix(dashboard): isNew compares timestamps numerically) gehärtet. Diese Tests müssen aktiv gelöscht werden, nicht nur ignoriert.

Recommendations

  • Acceptance-Criterium ergänzen (Section H): „Alle Visual-Regression-Baselines unter frontend/e2e/ für / werden im selben Commit gelöscht und neu generiert."
  • Acceptance-Criterium ergänzen: „frontend/e2e/reader-dashboard.spec.ts existiert; AxeBuilder({page}).withTags(['wcag2a','wcag2aa']).analyze() läuft für authentifizierten READ_ALL-User in light + dark; wiederholt für BLOG_WRITE-User; Resultat-Violations leer."
  • Acceptance-Criterium ergänzen: „page.svelte.spec.ts Reader-Branch-Test (covers the {#if data.isReader} render branch) wird aktualisiert, behält die Discriminant-Assertion, ersetzt Selektoren auf das neue Layout."
  • Acceptance-Criterium ergänzen: „Tests für entfernte NEW/UPDATED-Badges (in ReaderRecentDocs.svelte.spec.ts + dashboard_badge_new/dashboard_badge_updated i18n-Keys) werden gelöscht, nicht skipped."
  • Touch-Target-Konflikt (siehe Decision Queue): vor Implementation entscheiden, sonst landet das im Code-Review als Blocker.
  • TDD-Disziplin pro Komponente: Spec umbenennen → neue rote Spec basierend auf Section A–F-Strukturen schreiben → Markup grün ziehen. Reuse von vitest-browser-svelte render() + getByRole('grid') für das Person-Grid (Spec hat 4 Karten in 1 [role="grid"]-Container).

Open Decisions

  • Touch-Target ≥ 44 px vs. Spec-Density. Optionen: (A) min-h-[44px] auf Doc-Row, Story-Row, Draft-Row, Card-Head-Link, Stat-Spalten — bricht die Spec-Höhe, hält Mobile-AA. (B) Nur unterhalb sm min-h-[44px], ab sm Spec-Werte — kompromiss, mehr CSS. (C) Spec-Werte 1:1 — verstößt gegen Memory-Regel und WCAG 2.5.5. Empfehlung B, weil Read-Path mobil dominiert.
## 🧪 Sara Holt — QA Engineer & Test Strategist ### Observations - 5 Component-Specs (`Reader*.svelte.spec.ts`, 349 Zeilen) testen die **aktuelle** Struktur: Chip-Reihe (`ReaderPersonChips`), getrennte Stat-Karten (`ReaderStatsStrip`), NEW/UPDATED-Badges (`ReaderRecentDocs`), padded Cards mit `<h2>`. Sektionen A–F machen alle 5 Specs ungültig — Rewrite, nicht Patch. - `+page.svelte` hat eine Reader-Branch-Spec (`page.svelte.spec.ts`, commit `64bcc8d0`) die das aktuelle Layout assertiert. Auch zu rewriten. - Section H listet axe-Playwright auf `/` Reader light + dark + BLOG_WRITE. Aktuell existiert **keine** Reader-spezifische Playwright-Spec — das ist neue Test-Infrastruktur, kein Update. CI-Budget: ~1–2 min zusätzlich. - Visual-Regression bei 320 / 768 / 1440 px × {light, dark, BLOG_WRITE} = **9 neue Baselines**, nicht aktualisierte. Die Issue-Wording „neue Baselines werden eingecheckt" verschleiert, dass die alten gelöscht werden müssen. - Touch-Target-Konflikt: `Doc-Row px-3 py-1.5` und `Draft-Row px-3 py-1.5` ergeben Zeilenhöhen ≈ 24–28 px. Memory `feedback_a11y` und `project_user_split_transcribe_vs_read` sagen: Read-Path ist **Critical** auf Mobile, 44 px Touch-Target Pflicht. Die Spec-Werte verletzen das. Section H listet 44 px als Acceptance, Section D listet die Padding-Werte daneben — das passt nicht zusammen. - NEW/UPDATED-Badges in `ReaderRecentDocs.svelte` wurden mit eigenen Tests in `5a8a1898` (`fix(dashboard): isNew compares timestamps numerically`) gehärtet. Diese Tests müssen aktiv gelöscht werden, nicht nur ignoriert. ### Recommendations - Acceptance-Criterium ergänzen (Section H): „Alle Visual-Regression-Baselines unter `frontend/e2e/` für `/` werden im selben Commit gelöscht und neu generiert." - Acceptance-Criterium ergänzen: „`frontend/e2e/reader-dashboard.spec.ts` existiert; `AxeBuilder({page}).withTags(['wcag2a','wcag2aa']).analyze()` läuft für authentifizierten READ_ALL-User in light + dark; wiederholt für BLOG_WRITE-User; Resultat-Violations leer." - Acceptance-Criterium ergänzen: „`page.svelte.spec.ts` Reader-Branch-Test (`covers the {#if data.isReader} render branch`) wird aktualisiert, behält die Discriminant-Assertion, ersetzt Selektoren auf das neue Layout." - Acceptance-Criterium ergänzen: „Tests für entfernte NEW/UPDATED-Badges (in `ReaderRecentDocs.svelte.spec.ts` + `dashboard_badge_new`/`dashboard_badge_updated` i18n-Keys) werden gelöscht, nicht skipped." - Touch-Target-Konflikt (siehe Decision Queue): vor Implementation entscheiden, sonst landet das im Code-Review als Blocker. - TDD-Disziplin pro Komponente: Spec umbenennen → neue rote Spec basierend auf Section A–F-Strukturen schreiben → Markup grün ziehen. Reuse von `vitest-browser-svelte` `render()` + `getByRole('grid')` für das Person-Grid (Spec hat 4 Karten in 1 `[role="grid"]`-Container). ### Open Decisions - Touch-Target ≥ 44 px vs. Spec-Density. Optionen: (A) `min-h-[44px]` auf Doc-Row, Story-Row, Draft-Row, Card-Head-Link, Stat-Spalten — bricht die Spec-Höhe, hält Mobile-AA. (B) Nur unterhalb `sm` `min-h-[44px]`, ab `sm` Spec-Werte — kompromiss, mehr CSS. (C) Spec-Werte 1:1 — verstößt gegen Memory-Regel und WCAG 2.5.5. Empfehlung B, weil Read-Path mobil dominiert.
Author
Owner

🎨 Leonie Voss — UI/UX Design Lead

Observations

  • Spec ist gut. Issue ist gründlich. Drei Stellen brauchen Schärfung, eine ist a11y-blockend.
  • Touch-Target-Verletzung (Critical): Doc-Row px-3 py-1.5 (~24 px), Story-Row px-3 py-2 (~32 px), Draft-Row px-3 py-1.5, „Alle ..."-Card-Head-Link text-[11px] ohne Höhenangabe. Auf Mobile (Read-Path = Critical, Memory project_user_split_transcribe_vs_read) muss jede tappable Region ≥ 44 px sein. Spec-Mockup-Dichte gilt für Desktop; auf Mobile zwingend min-h-[44px].
  • Hex-Streuung in Sektionen B/C/D ohne Token-Anker:
    • #C8B8A0 (Greeting-Time-Label) — warm-beige, nicht im Palette → Token-Kandidat --c-greeting-time.
    • #F0EDE6 (Stat-Spalten-Divider, Card-Head-Border, Row-Border) — Subline → --c-line-soft.
    • #4A6E8A („Alle Personen →"-Link) — gedämpfter Blau, AA-Kontrast 4,65:1 auf bg-canvas. Token-Kandidat --c-link-quiet.
    • #D4F0EE (Mint-Pill-BG) — Spec selbst sagt „Token-Kandidat --color-mint-pill". Pflicht-Token.
    • #ECEAE4 (Doc-Thumb-BG) — fast identisch mit --c-canvas-Light (#f0efe9). Wahrscheinlich bg-canvas reuse.
  • Avatar-Schatten im Dark-Mode: shadow-sm auf dark:bg-surface (#011526) ist quasi unsichtbar. Spec adressiert das nicht. Empfehlung: dark:shadow-none dark:ring-1 dark:ring-white/10 für äquivalente visuelle Tiefe.
  • Person-Grid ohne Heading: Spec hat keinen sichtbaren <h2>. Issue Sektion C entfernt ihn auch. Screen-Reader verlieren dann den Sektions-Anker. Lösung: <section aria-label={m.dashboard_reader_person_chips_heading()}> um das Grid — visuell unverändert, SR-Navigation erhalten.
  • NEW/UPDATED-Badges-Streichung (Section D, letzter Bullet): Diese Badges sind in 3 jüngsten Commits (7bd477d2, f7eefb52, 5a8a1898) bewusst gehärtet worden — Touch-Target, Kontrast, Timestamp-Vergleich. Spec ist älter als diese Entscheidung. Vor dem Streichen: bestätigen, dass Spec weiterhin gilt — oder Mint-Pill als „Aktualisiert"-Marker im neuen Layout zulassen.
  • „Alle N Personen →"-Link: Spec verlangt text-[#4A6E8A] no-underline, Issue ebenso. Im Dark-Mode (Spec Z. 185) wechselt das auf #A6DAD8 65 %. Issue Section G muss das explizit machen — sonst landet die Underline aus dem alten Pattern wieder drin.
  • Stat-Strip mobil stacked: Section A/B beschreiben border-t pt-1.5. Inline-Trenner zwischen 3 Stat-Spalten fehlen — Mockup zeigt sie. Empfehlung: divide-x divide-line/50 auf dem Stats-Container.
  • Greeting-Tagline-Label (text-[11px] uppercase) auf weißem Header: #C8B8A0 ergibt 2,8:1 auf #ffffällt unter AA-Floor 4,5:1. Spec selbst hat den Wert vorgegeben, aber er besteht WCAG nicht. Entweder dunkler (#9d8a6a ≈ 4,8:1) oder bewusst akzeptieren als „decorative tag" mit aria-hidden="true". Bitte messen, nicht raten.

Recommendations

  • Tokens-Erweiterung in layout.css als ersten Commit (vor allem Markup), Block in @theme inline:
    --c-greeting-time: light TBD-AA-pass / dark #4a5880
    --c-line-soft:     #f0ede6 / rgba(255,255,255,.06)
    --c-link-quiet:    #4a6e8a / var(--palette-mint) at 65% (use rgba)
    --palette-mint-soft: #d4f0ee   (raw palette, used as bg-mint-soft)
    
  • Acceptance-Criterium ergänzen (Section A): „Alle interaktiven Regionen (Card-Head-Links, Stat-Spalten, Doc-Row, Story-Row, Draft-Row, Person-Card, Alle-Links) erfüllen min-h-[44px] ab Mobile (< sm); Desktop folgt der Spec-Höhe."
  • Acceptance-Criterium ergänzen (Section B): „Greeting-Time-Label-Kontrast bestätigt ≥ 4,5:1 auf weißem Header — Spec-Hex #C8B8A0 (2,8:1) wird nicht übernommen. Ersatz-Hex oder Token in PR dokumentiert."
  • Acceptance-Criterium ergänzen (Section C): „Person-Grid wird in <section aria-label=…> gewrappt; visuelles <h2> entfällt wie in Spec, SR-Anker bleibt erhalten."
  • Acceptance-Criterium ergänzen (Section G): „Avatar-Schatten im Dark-Mode wird durch dark:shadow-none dark:ring-1 dark:ring-white/10 ersetzt."
  • Acceptance-Criterium ergänzen (Section H): „axe-Kontrastprüfung läuft in Dark- UND Light-Mode auf Mobile-Viewport (320 px), nicht nur Desktop."
  • Mobile-Stats: divide-x divide-line/50 als Inline-Separator-Variante zur Spec hinzufügen.

Open Decisions

  • NEW/UPDATED-Badges in ReaderRecentDocs: Spec ohne Badges vs. drei aktuelle a11y-Commits, die sie hinzugefügt + gehärtet haben. Optionen: (A) Streichen wie Spec — beste Spec-Treue, opfert sichtbares „neu"-Signal. (B) Behalten — bricht Spec-Compliance der ganzen Card-Sprache. (C) In Mint-Pill umwandeln — passt ins neue Visual ohne Spec-Bruch (Mint-Pill ist eh eingeführt für Person-Count). Empfehlung C, weil semantischer Mehrwert ohne visuelle Inkonsistenz.
## 🎨 Leonie Voss — UI/UX Design Lead ### Observations - Spec ist gut. Issue ist gründlich. Drei Stellen brauchen Schärfung, eine ist a11y-blockend. - **Touch-Target-Verletzung (Critical)**: `Doc-Row px-3 py-1.5` (~24 px), `Story-Row px-3 py-2` (~32 px), `Draft-Row px-3 py-1.5`, „Alle ..."-Card-Head-Link `text-[11px]` ohne Höhenangabe. Auf Mobile (Read-Path = Critical, Memory `project_user_split_transcribe_vs_read`) muss jede tappable Region ≥ 44 px sein. Spec-Mockup-Dichte gilt für Desktop; auf Mobile zwingend `min-h-[44px]`. - **Hex-Streuung** in Sektionen B/C/D ohne Token-Anker: - `#C8B8A0` (Greeting-Time-Label) — warm-beige, nicht im Palette → Token-Kandidat `--c-greeting-time`. - `#F0EDE6` (Stat-Spalten-Divider, Card-Head-Border, Row-Border) — Subline → `--c-line-soft`. - `#4A6E8A` („Alle Personen →"-Link) — gedämpfter Blau, AA-Kontrast 4,65:1 auf `bg-canvas`. Token-Kandidat `--c-link-quiet`. - `#D4F0EE` (Mint-Pill-BG) — Spec selbst sagt „Token-Kandidat `--color-mint-pill`". Pflicht-Token. - `#ECEAE4` (Doc-Thumb-BG) — fast identisch mit `--c-canvas`-Light (`#f0efe9`). Wahrscheinlich `bg-canvas` reuse. - **Avatar-Schatten im Dark-Mode**: `shadow-sm` auf `dark:bg-surface` (`#011526`) ist quasi unsichtbar. Spec adressiert das nicht. Empfehlung: `dark:shadow-none dark:ring-1 dark:ring-white/10` für äquivalente visuelle Tiefe. - **Person-Grid ohne Heading**: Spec hat keinen sichtbaren `<h2>`. Issue Sektion C entfernt ihn auch. Screen-Reader verlieren dann den Sektions-Anker. Lösung: `<section aria-label={m.dashboard_reader_person_chips_heading()}>` um das Grid — visuell unverändert, SR-Navigation erhalten. - **NEW/UPDATED-Badges-Streichung** (Section D, letzter Bullet): Diese Badges sind in 3 jüngsten Commits (`7bd477d2`, `f7eefb52`, `5a8a1898`) bewusst gehärtet worden — Touch-Target, Kontrast, Timestamp-Vergleich. Spec ist älter als diese Entscheidung. Vor dem Streichen: bestätigen, dass Spec weiterhin gilt — oder Mint-Pill als „Aktualisiert"-Marker im neuen Layout zulassen. - **„Alle N Personen →"-Link**: Spec verlangt `text-[#4A6E8A] no-underline`, Issue ebenso. Im Dark-Mode (Spec Z. 185) wechselt das auf `#A6DAD8` 65 %. Issue Section G muss das explizit machen — sonst landet die Underline aus dem alten Pattern wieder drin. - **Stat-Strip mobil stacked**: Section A/B beschreiben `border-t pt-1.5`. Inline-Trenner zwischen 3 Stat-Spalten fehlen — Mockup zeigt sie. Empfehlung: `divide-x divide-line/50` auf dem Stats-Container. - **Greeting-Tagline-Label** (`text-[11px] uppercase`) auf weißem Header: `#C8B8A0` ergibt **2,8:1** auf `#fff` — **fällt unter AA-Floor 4,5:1**. Spec selbst hat den Wert vorgegeben, aber er besteht WCAG nicht. Entweder dunkler (`#9d8a6a` ≈ 4,8:1) oder bewusst akzeptieren als „decorative tag" mit `aria-hidden="true"`. Bitte messen, nicht raten. ### Recommendations - **Tokens-Erweiterung in `layout.css` als ersten Commit** (vor allem Markup), Block in `@theme inline`: ``` --c-greeting-time: light TBD-AA-pass / dark #4a5880 --c-line-soft: #f0ede6 / rgba(255,255,255,.06) --c-link-quiet: #4a6e8a / var(--palette-mint) at 65% (use rgba) --palette-mint-soft: #d4f0ee (raw palette, used as bg-mint-soft) ``` - **Acceptance-Criterium ergänzen** (Section A): „Alle interaktiven Regionen (Card-Head-Links, Stat-Spalten, Doc-Row, Story-Row, Draft-Row, Person-Card, Alle-Links) erfüllen `min-h-[44px]` ab Mobile (`< sm`); Desktop folgt der Spec-Höhe." - **Acceptance-Criterium ergänzen** (Section B): „Greeting-Time-Label-Kontrast bestätigt ≥ 4,5:1 auf weißem Header — Spec-Hex `#C8B8A0` (2,8:1) wird **nicht** übernommen. Ersatz-Hex oder Token in PR dokumentiert." - **Acceptance-Criterium ergänzen** (Section C): „Person-Grid wird in `<section aria-label=…>` gewrappt; visuelles `<h2>` entfällt wie in Spec, SR-Anker bleibt erhalten." - **Acceptance-Criterium ergänzen** (Section G): „Avatar-Schatten im Dark-Mode wird durch `dark:shadow-none dark:ring-1 dark:ring-white/10` ersetzt." - **Acceptance-Criterium ergänzen** (Section H): „axe-Kontrastprüfung läuft in Dark- UND Light-Mode auf Mobile-Viewport (320 px), nicht nur Desktop." - Mobile-Stats: `divide-x divide-line/50` als Inline-Separator-Variante zur Spec hinzufügen. ### Open Decisions - NEW/UPDATED-Badges in `ReaderRecentDocs`: Spec ohne Badges vs. drei aktuelle a11y-Commits, die sie hinzugefügt + gehärtet haben. Optionen: (A) Streichen wie Spec — beste Spec-Treue, opfert sichtbares „neu"-Signal. (B) Behalten — bricht Spec-Compliance der ganzen Card-Sprache. (C) In Mint-Pill umwandeln — passt ins neue Visual ohne Spec-Bruch (Mint-Pill ist eh eingeführt für Person-Count). Empfehlung C, weil semantischer Mehrwert ohne visuelle Inkonsistenz.
Author
Owner

🛠️ Tobias Wendt — DevOps & Platform Engineer

Observations

  • Reiner Frontend-Refactor. Keine docker-compose.yml-Änderung, kein neues Service, keine neue Migration, keine Caddy-/Reverse-Proxy-Anpassung, keine Secret-Rotation.
  • CI-Budget: Section H führt axe-Playwright und Visual-Regression auf 3 Breakpoints × 3 Reader-Varianten ein. Bei pessimistischer Schätzung +60–90 s auf der e2e-Job-Dauer. Aktuell rund 6–7 min — bleibt unter dem 8-min-Ziel.
  • Visual-Regression-Snapshots sind binäre Artefakte. Bei 9 Baselines × 2 Themes wachsen sie ~1–2 MB pro Branch. Acceptable, falls die Baselines im Repo (nicht via Gitea LFS) gehalten werden.

Recommendations

  • Keine Infrastruktur-Aktion erforderlich. Falls Visual-Regression im CI flaky wird (Font-Rendering-Drift zwischen lokal und Runner), --update-snapshots auf der CI-Maschine einmalig ausführen, dann pinnen. Diese Sorte Drift sehe ich bei Playwright auf Hetzner-Runnern gelegentlich.
  • Wenn Snapshot-Größe stört (unwahrscheinlich), lassen sich Reader-Snapshots in frontend/e2e/__snapshots__/reader-dashboard/ isolieren — vereinfacht spätere Bereinigung.

Open Decisions

(none from this angle)

## 🛠️ Tobias Wendt — DevOps & Platform Engineer ### Observations - Reiner Frontend-Refactor. Keine `docker-compose.yml`-Änderung, kein neues Service, keine neue Migration, keine Caddy-/Reverse-Proxy-Anpassung, keine Secret-Rotation. - CI-Budget: Section H führt axe-Playwright und Visual-Regression auf 3 Breakpoints × 3 Reader-Varianten ein. Bei pessimistischer Schätzung +60–90 s auf der `e2e`-Job-Dauer. Aktuell rund 6–7 min — bleibt unter dem 8-min-Ziel. - Visual-Regression-Snapshots sind binäre Artefakte. Bei 9 Baselines × 2 Themes wachsen sie ~1–2 MB pro Branch. Acceptable, falls die Baselines im Repo (nicht via Gitea LFS) gehalten werden. ### Recommendations - Keine Infrastruktur-Aktion erforderlich. Falls Visual-Regression im CI flaky wird (Font-Rendering-Drift zwischen lokal und Runner), `--update-snapshots` auf der CI-Maschine einmalig ausführen, dann pinnen. Diese Sorte Drift sehe ich bei Playwright auf Hetzner-Runnern gelegentlich. - Wenn Snapshot-Größe stört (unwahrscheinlich), lassen sich Reader-Snapshots in `frontend/e2e/__snapshots__/reader-dashboard/` isolieren — vereinfacht spätere Bereinigung. ### Open Decisions _(none from this angle)_
Author
Owner

🗳️ Decision Queue — Action Required

3 Entscheidungen vor Implementation-Start.

UX / Accessibility

  • Touch-Target ≥ 44 px vs. Spec-Density
    Spec verlangt Doc-Row px-3 py-1.5 (~24 px), Story-Row px-3 py-2 (~32 px), Draft-Row px-3 py-1.5 und kompakte Card-Head-Links — alle unter dem 44-px-Floor. Memory project_user_split_transcribe_vs_read setzt Read-Path auf Mobile auf Critical.
    Optionen:

    • (A) min-h-[44px] auf alle interaktiven Rows + Stat-Spalten + Card-Head-Links — bricht die Spec-Höhen, hält WCAG 2.5.5 (AA).
    • (B) min-h-[44px] nur unterhalb sm, ab sm Spec-Werte — kompromittierter Mittelweg, Mobile compliant, Desktop spec-treu.
    • (C) Spec 1:1 — verstößt gegen die Memory-Regel und WCAG 2.5.5.
      Empfehlung der Personas: B.
      (Raised by: Sara, Leonie)
  • NEW / UPDATED-Badges in ReaderRecentDocs
    Spec hat keine Badges. Aber drei jüngste Commits haben sie eingeführt + a11y-gehärtet (5a8a1898 Timestamp-Vergleich, f7eefb52 Kontrast, 7bd477d2 44-px-Touch). Spec ist älter als diese Entscheidung.
    Optionen:

    • (A) Streichen wie Spec — opfert sichtbares „neu"-Signal, full Spec-Compliance.
    • (B) Behalten — bricht Spec-Compliance der Card-Sprache.
    • (C) Als Mint-Pill umsetzen — kompatibel mit dem neu eingeführten Pill-Pattern (Person-Count).
      Empfehlung der Personas: C.
      (Raised by: Sara, Leonie)
  • Greeting-Time-Label #C8B8A0 auf #fff
    Spec verlangt diese Kombination. Real ergibt das 2,8:1 — fällt unter AA-Floor 4,5:1 für Normal-Text.
    Optionen:

    • (A) Dunkler Ersatz-Hex (#9d8a6a ≈ 4,8:1) — minimaler Drift vom Spec-Look.
    • (B) aria-hidden="true" und als rein dekoratives Tag akzeptieren — visuell exakt Spec, SR-unsichtbar (Tageszeit ist auch im Hauptgreeting redundant erkennbar).
    • (C) Spec-Hex 1:1 — WCAG-Verletzung.
      Beide Personas (Leonie + Sara) verlangen Auflösung vor Implementation. Kein klarer Empfehlungsoption — hängt davon ab, ob die Tageszeit-Information auf SR-Hörern wichtig ist.
      (Raised by: Leonie)
## 🗳️ Decision Queue — Action Required _3 Entscheidungen vor Implementation-Start._ ### UX / Accessibility - **Touch-Target ≥ 44 px vs. Spec-Density** Spec verlangt `Doc-Row px-3 py-1.5` (~24 px), `Story-Row px-3 py-2` (~32 px), `Draft-Row px-3 py-1.5` und kompakte Card-Head-Links — alle unter dem 44-px-Floor. Memory `project_user_split_transcribe_vs_read` setzt Read-Path auf Mobile auf **Critical**. Optionen: - **(A)** `min-h-[44px]` auf alle interaktiven Rows + Stat-Spalten + Card-Head-Links — bricht die Spec-Höhen, hält WCAG 2.5.5 (AA). - **(B)** `min-h-[44px]` nur unterhalb `sm`, ab `sm` Spec-Werte — kompromittierter Mittelweg, Mobile compliant, Desktop spec-treu. - **(C)** Spec 1:1 — verstößt gegen die Memory-Regel und WCAG 2.5.5. Empfehlung der Personas: **B**. _(Raised by: Sara, Leonie)_ - **NEW / UPDATED-Badges in `ReaderRecentDocs`** Spec hat keine Badges. Aber drei jüngste Commits haben sie eingeführt + a11y-gehärtet (`5a8a1898` Timestamp-Vergleich, `f7eefb52` Kontrast, `7bd477d2` 44-px-Touch). Spec ist älter als diese Entscheidung. Optionen: - **(A)** Streichen wie Spec — opfert sichtbares „neu"-Signal, full Spec-Compliance. - **(B)** Behalten — bricht Spec-Compliance der Card-Sprache. - **(C)** Als Mint-Pill umsetzen — kompatibel mit dem neu eingeführten Pill-Pattern (Person-Count). Empfehlung der Personas: **C**. _(Raised by: Sara, Leonie)_ - **Greeting-Time-Label `#C8B8A0` auf `#fff`** Spec verlangt diese Kombination. Real ergibt das **2,8:1** — fällt unter AA-Floor 4,5:1 für Normal-Text. Optionen: - **(A)** Dunkler Ersatz-Hex (`#9d8a6a` ≈ 4,8:1) — minimaler Drift vom Spec-Look. - **(B)** `aria-hidden="true"` und als rein dekoratives Tag akzeptieren — visuell exakt Spec, SR-unsichtbar (Tageszeit ist auch im Hauptgreeting redundant erkennbar). - **(C)** Spec-Hex 1:1 — WCAG-Verletzung. Beide Personas (Leonie + Sara) verlangen Auflösung vor Implementation. Kein klarer Empfehlungsoption — hängt davon ab, ob die Tageszeit-Information auf SR-Hörern wichtig ist. _(Raised by: Leonie)_
Author
Owner

Touch Target: A
Badges: C
Greeting: We also use navy blue (token, so it is compatible with darkmode)

Touch Target: A Badges: C Greeting: We also use navy blue (token, so it is compatible with darkmode)
Author
Owner

Implementation complete

All 8 steps implemented on branch feat/issue-483-reader-dashboard-spec-align.

Commits

Commit What
49521005 style: add mint-soft, line-soft, link-quiet, ink-4 CSS tokens
2f48dfab i18n: 9 new keys, delete dashboard_badge_updated
b5f9fcfd feat: new ReaderHeaderBar — greeting + 3 stat columns (TDD)
ae6355d2 refactor: ReaderPersonChips → grid layout, mint-pill doc count (TDD)
e1c78e3f refactor: ReaderRecentDocs compact card-head, mint-pill "Neu" badge (TDD)
e598f5a5 refactor: ReaderRecentStories card-head link, touch targets (TDD)
60326cfb refactor: ReaderDraftsModule mint left-border, card-head, chevron (TDD)
43d36c89 feat: wire ReaderHeaderBar into +page.svelte, grid content-row, delete ReaderStatsStrip

Verification

  • Tests: 1835 passed / 0 failed across 190 test files
  • Type check: 277 errors — all pre-existing in unrelated files (none in our components)
  • Build: blocked by pre-existing root-owned src/lib/paraglide/ directory (Vite can't delete root files during recompile); not caused by this PR

Decision Queue resolutions applied

  • Touch target: min-h-[44px] on all interactive regions throughout
  • Badges: "Neu" → Mint-Pill; "Aktualisiert" badge removed entirely
  • Greeting time label: text-brand-navy (token, dark-mode safe)
## Implementation complete ✅ All 8 steps implemented on branch `feat/issue-483-reader-dashboard-spec-align`. ### Commits | Commit | What | |--------|------| | `49521005` | `style`: add mint-soft, line-soft, link-quiet, ink-4 CSS tokens | | `2f48dfab` | `i18n`: 9 new keys, delete `dashboard_badge_updated` | | `b5f9fcfd` | `feat`: new `ReaderHeaderBar` — greeting + 3 stat columns (TDD) | | `ae6355d2` | `refactor`: `ReaderPersonChips` → grid layout, mint-pill doc count (TDD) | | `e1c78e3f` | `refactor`: `ReaderRecentDocs` compact card-head, mint-pill "Neu" badge (TDD) | | `e598f5a5` | `refactor`: `ReaderRecentStories` card-head link, touch targets (TDD) | | `60326cfb` | `refactor`: `ReaderDraftsModule` mint left-border, card-head, chevron (TDD) | | `43d36c89` | `feat`: wire `ReaderHeaderBar` into `+page.svelte`, grid content-row, delete `ReaderStatsStrip` | ### Verification - **Tests**: 1835 passed / 0 failed across 190 test files - **Type check**: 277 errors — all pre-existing in unrelated files (none in our components) - **Build**: blocked by pre-existing root-owned `src/lib/paraglide/` directory (Vite can't delete root files during recompile); not caused by this PR ### Decision Queue resolutions applied - Touch target: `min-h-[44px]` on all interactive regions throughout - Badges: "Neu" → Mint-Pill; "Aktualisiert" badge removed entirely - Greeting time label: `text-brand-navy` (token, dark-mode safe)
Sign in to join this conversation.
No Label P1-high bug ui
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#483