fix(dashboard): align reader dashboard with reader-dashboard-final spec #483
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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 Specdocs/specs/reader-dashboard-final.htmlab (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*.sveltefür einen einzigen Refactor-Schritt.Quellen
docs/specs/reader-dashboard-final.htmlsm= 640 px)frontend/src/routes/+page.svelte({#if data.isReader}-Branch) +Reader{StatsStrip,PersonChips,DraftsModule,RecentDocs,RecentStories}.svelteAcceptance Criteria
A. Layout / Reihenfolge
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.svelteentfällt.grid grid-cols-1 sm:grid-cols-2 gap-1.5(1 : 1) — nichtflex-[3] / flex-[2].max-w-7xl mx-auto px-8 py-8(kein responsivespx-4 sm:px-6 lg:px-8).B. Header-Balken
text-[11px] font-bold uppercase tracking-[.8px] text-[#C8B8A0]) überfont-serif text-xl text-brand-navy„Herzlich willkommen, {name}.".border-r border-[#F0EDE6](außer letzte). Zahltext-2xl font-black text-brand-navy leading-none block. Label daruntertext-[11px] font-bold uppercase tracking-[.8px] text-ink-3 block mt-0.5.w-px self-stretch bg-line flex-shrink-0.border border-line bg-surface shadow-smpro Stat, keinhover:border-brand-mint).border-t pt-1.5. Stat-Labels nutzen Kurzform „Dok.", „Pers.", „Gesch.".dashboard.greeting.morning/afternoon/evening(nur Tageszeit, ohne Name)dashboard.welcomeparametrisch („Herzlich willkommen, {name}.")dashboard.stat.documents/persons/storiesmit mobiler Kurzform-Variantedashboard.recentlyUpdated,dashboard.myDrafts,dashboard.persons.viewAllgreeting_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).bg-surface border border-line rounded-sm p-2.5 no-underline. Keinborder-l-3 border-l-brand-mint, kein Bottom-Akzent, keinhover:border-brand-mint.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).w-[22px] h-[22px], kleinere Schrift.text-[11px] font-bold text-brand-navy bg-[#D4F0EE] px-1.5 py-px rounded-full(statt plaintext-ink-3).<h2>{m.dashboard_reader_person_chips_heading()}</h2>entfällt — der Block startet direkt mit dem Grid (Spec hat keinen Heading).text-xs font-semibold text-[#4A6E8A] no-underline text-right block mt-1. Keineunderline, keintext-brand-navy.D. Recent-Docs Card („Zuletzt aktualisiert")
bg-surface border border-line rounded-sm overflow-hidden flex flex-col. Keinp-6, keinshadow-sm.flex items-center justify-between px-3 py-1.5 border-b border-line. h3 intext-[11px] font-bold uppercase tracking-[.12em] text-ink-3. Link „Alle Dokumente" rechts:text-[11px] font-semibold text-brand-navy/40 no-underline.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).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.text-xs text-ink-3mit Präfix „von "; bei fehlendem Sender wird Em-Dash „—" intext-lineangezeigt (nicht ausblenden).updatedAt).<h2>entfällt — Heading sitzt ausschließlich im Card-Head.E. Geschichten Card
px-3 py-2 border-b border-line/50 last:border-b-0. Aufbau vertikal: Titel → Excerpt → Meta.font-serif text-base italic text-ink.text-xs text-ink-2 leading-relaxed line-clamp-2(Tokenink-2, nichtink-3).text-[11px] text-ink-3.{#if stories.length > 0}Wrapper bleibt (Card wird bei leerer Liste ausgeblendet).F. Drafts-Card (BLOG_WRITE)
border-l-[3px] border-l-brand-mintzusätzlich zuborder border-line.border-b), kein „Alle"-Link, h3 = „Meine Entwürfe".flex items-center justify-between px-3 py-1.5 border-b border-line/50 last:border-b-0.font-serif text-sm text-brand-navy+ Metatext-[11px] text-ink-3mit Format „Entwurf · zuletzt bearbeitet {relative}".<svg width=7 height=7>).<h2>entfällt.G. Dark-Mode
dark:bg-surface dark:border-white/8(über die existierenden Tailwind-dark:-Token — nicht die!important-Override-Klassen aus der Spec).dark:text-ink-4(#6070A0, AA-konform).dark:text-ink-5(#5A6888).dark:bg-brand-mint/15 dark:text-brand-mint.dark:text-ink-3(#7080A8).H. Tests / NFRs
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)./(Reader-Variante, Light + Dark) bleibt grün. Geprüfte Kontraste:text-ink-3 #706C68≥ 4.5:1 aufbg-canvas#4A6E8A≥ 4.5:1 aufbg-canvas#002850auf#D4F0EE≥ 4.5:1I. Out of scope
+page.server.ts-Loader, keine neuen DTOs, keine Backend-Änderungen.{:else}in+page.svelte) — das betrifft nur den Reader.d554fc7ebleibt erhalten — bei Bedarf eigenes Folge-Issue.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. inlayout.cssergänzt werden, falls sie noch nicht existieren — Token-Abgleich vor der Implementierung prüfen, sonst entstehen unaufgelöste Tailwind-Klassen.👨💻 Felix Brandt — Senior Fullstack Developer
Observations
dark:text-ink-4(#6070A0) unddark:text-ink-5(#5A6888). Diese Token existieren nicht infrontend/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.#C8B8A0,#F0EDE6,#4A6E8A,#ECEAE4,#D4F0EE). Das umgeht die@theme inline-Disziplin inlayout.cssund macht die Cards für Dark-Mode-Wechsel unsichtbar.dashboard_reader_*-Keys existieren bereits (messages/{de,en,es}.json449–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.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.ReaderRecentDocs.sveltewurden gerade erst poliert (5a8a1898ISO-vs-numeric,f7eefb52AA-Kontrast,7bd477d2Touch-Target). Section D verlangt deren ersatzlose Streichung — das ist eine bewusste Rücknahme von 3 frischen Commits.Recommendations
layout.cssergä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.--c-ink-4,--c-ink-5(light + dark) ergänzen, sonst Sektion G auftext-ink-2/text-ink-3reduzieren.dashboard_greeting_time_morning/afternoon/evening,dashboard_stat_documents_shortetc.). Migration ist Reibung ohne Mehrwert.ReaderHeaderBar(neu) →ReaderPersonGrid(umbau aus PersonChips) →ReaderRecentDocs→ReaderRecentStories→ReaderDraftsModule.ReaderHeaderBar.svelteextrahieren — die Greeting+Stats-Bar braucht keine Logik in+page.svelteund ist isoliert testbar.bg-white(stattbg-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
dashboard.greeting.morning(ohne Name), aktuell existiertgreeting_morning(mit Name). Ersetzen heißt 13 weitere Keys + Spec-Compliance, Ergänzen heißt 3 neue Keys + ein Mix von zwei Konventionen.🏛️ Markus Keller — Application Architect
Observations
+page.server.ts, Loader bleiben unangetastet — Section I bestätigt das. Keine Modul-Boundary-Implikationen.data.isReader) bleibt 1:1 gültig — kein neues ADR erforderlich.layout.css) erweitert wird. Tokens sind Architektur-Kontrakt — sie gehören in@theme inline, nicht in den Komponenten als arbiträre Hex-Werte.bg-whitestattbg-surfaceist eine bewusste Spec-Entscheidung (B.1, Spec Z. 1131). Das ist keine Ebenen-Verletzung, aber es bricht das im Projekt etablierte Card-Pattern ausCLAUDE.md(bg-surface). Eine Inline-Kommentar-Notiz reicht — kein ADR.Recommendations
frontend/src/routes/layout.css(@theme inline) registriert. Komponentenmarkup verwendet ausschließlich Tailwind-Token-Klassen, keinebg-[#…]-Arbitrary-Values."--c-ink-4/--c-ink-5real inlayout.cssergänzen (mit AA-Kontrast-Kommentar wie für--c-ink-3schon vorhanden), oder die Sektion auf existierendetext-ink-2/text-ink-3reduzieren. Kein Issue darf undefined Tailwind-Klassen vorschreiben.ReaderRecentDocsundReaderRecentStorieshaben 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.🛡️ Nora "NullX" Steiner — Application Security Engineer
Observations
+page.server.tsund der Reader-Loader-Pfad bleiben laut Section I unverändert — Permission-Boundary (READ_ALLohneWRITE_ALL/ANNOTATE_ALL) wird durch ADR-007 erzwungen, das bleibt valid.target="_blank"-Links in Spec —rel="noopener noreferrer"nicht relevant.Recommendations
<a href={…}>mit user-controlled Daten einführt (z. B.personAvatarColorals inline-Style),npm run lintdeckt nichts davon ab — kurzes Manual-Review der Templates reicht.🧪 Sara Holt — QA Engineer & Test Strategist
Observations
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.sveltehat eine Reader-Branch-Spec (page.svelte.spec.ts, commit64bcc8d0) die das aktuelle Layout assertiert. Auch zu rewriten./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.Doc-Row px-3 py-1.5undDraft-Row px-3 py-1.5ergeben Zeilenhöhen ≈ 24–28 px. Memoryfeedback_a11yundproject_user_split_transcribe_vs_readsagen: 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.ReaderRecentDocs.sveltewurden mit eigenen Tests in5a8a1898(fix(dashboard): isNew compares timestamps numerically) gehärtet. Diese Tests müssen aktiv gelöscht werden, nicht nur ignoriert.Recommendations
frontend/e2e/für/werden im selben Commit gelöscht und neu generiert."frontend/e2e/reader-dashboard.spec.tsexistiert;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."page.svelte.spec.tsReader-Branch-Test (covers the {#if data.isReader} render branch) wird aktualisiert, behält die Discriminant-Assertion, ersetzt Selektoren auf das neue Layout."ReaderRecentDocs.svelte.spec.ts+dashboard_badge_new/dashboard_badge_updatedi18n-Keys) werden gelöscht, nicht skipped."vitest-browser-svelterender()+getByRole('grid')für das Person-Grid (Spec hat 4 Karten in 1[role="grid"]-Container).Open Decisions
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 unterhalbsmmin-h-[44px], absmSpec-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.🎨 Leonie Voss — UI/UX Design Lead
Observations
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-Linktext-[11px]ohne Höhenangabe. Auf Mobile (Read-Path = Critical, Memoryproject_user_split_transcribe_vs_read) muss jede tappable Region ≥ 44 px sein. Spec-Mockup-Dichte gilt für Desktop; auf Mobile zwingendmin-h-[44px].#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 aufbg-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). Wahrscheinlichbg-canvasreuse.shadow-smaufdark:bg-surface(#011526) ist quasi unsichtbar. Spec adressiert das nicht. Empfehlung:dark:shadow-none dark:ring-1 dark:ring-white/10für äquivalente visuelle Tiefe.<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.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.text-[#4A6E8A] no-underline, Issue ebenso. Im Dark-Mode (Spec Z. 185) wechselt das auf#A6DAD865 %. Issue Section G muss das explizit machen — sonst landet die Underline aus dem alten Pattern wieder drin.border-t pt-1.5. Inline-Trenner zwischen 3 Stat-Spalten fehlen — Mockup zeigt sie. Empfehlung:divide-x divide-line/50auf dem Stats-Container.text-[11px] uppercase) auf weißem Header:#C8B8A0ergibt 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" mitaria-hidden="true". Bitte messen, nicht raten.Recommendations
layout.cssals ersten Commit (vor allem Markup), Block in@theme inline:min-h-[44px]ab Mobile (< sm); Desktop folgt der Spec-Höhe."#C8B8A0(2,8:1) wird nicht übernommen. Ersatz-Hex oder Token in PR dokumentiert."<section aria-label=…>gewrappt; visuelles<h2>entfällt wie in Spec, SR-Anker bleibt erhalten."dark:shadow-none dark:ring-1 dark:ring-white/10ersetzt."divide-x divide-line/50als Inline-Separator-Variante zur Spec hinzufügen.Open Decisions
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.🛠️ Tobias Wendt — DevOps & Platform Engineer
Observations
docker-compose.yml-Änderung, kein neues Service, keine neue Migration, keine Caddy-/Reverse-Proxy-Anpassung, keine Secret-Rotation.e2e-Job-Dauer. Aktuell rund 6–7 min — bleibt unter dem 8-min-Ziel.Recommendations
--update-snapshotsauf der CI-Maschine einmalig ausführen, dann pinnen. Diese Sorte Drift sehe ich bei Playwright auf Hetzner-Runnern gelegentlich.frontend/e2e/__snapshots__/reader-dashboard/isolieren — vereinfacht spätere Bereinigung.Open Decisions
(none from this angle)
🗳️ 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.5und kompakte Card-Head-Links — alle unter dem 44-px-Floor. Memoryproject_user_split_transcribe_vs_readsetzt Read-Path auf Mobile auf Critical.Optionen:
min-h-[44px]auf alle interaktiven Rows + Stat-Spalten + Card-Head-Links — bricht die Spec-Höhen, hält WCAG 2.5.5 (AA).min-h-[44px]nur unterhalbsm, absmSpec-Werte — kompromittierter Mittelweg, Mobile compliant, Desktop spec-treu.Empfehlung der Personas: B.
(Raised by: Sara, Leonie)
NEW / UPDATED-Badges in
ReaderRecentDocsSpec hat keine Badges. Aber drei jüngste Commits haben sie eingeführt + a11y-gehärtet (
5a8a1898Timestamp-Vergleich,f7eefb52Kontrast,7bd477d244-px-Touch). Spec ist älter als diese Entscheidung.Optionen:
Empfehlung der Personas: C.
(Raised by: Sara, Leonie)
Greeting-Time-Label
#C8B8A0auf#fffSpec verlangt diese Kombination. Real ergibt das 2,8:1 — fällt unter AA-Floor 4,5:1 für Normal-Text.
Optionen:
#9d8a6a≈ 4,8:1) — minimaler Drift vom Spec-Look.aria-hidden="true"und als rein dekoratives Tag akzeptieren — visuell exakt Spec, SR-unsichtbar (Tageszeit ist auch im Hauptgreeting redundant erkennbar).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)
Touch Target: A
Badges: C
Greeting: We also use navy blue (token, so it is compatible with darkmode)
Implementation complete ✅
All 8 steps implemented on branch
feat/issue-483-reader-dashboard-spec-align.Commits
49521005style: add mint-soft, line-soft, link-quiet, ink-4 CSS tokens2f48dfabi18n: 9 new keys, deletedashboard_badge_updatedb5f9fcfdfeat: newReaderHeaderBar— greeting + 3 stat columns (TDD)ae6355d2refactor:ReaderPersonChips→ grid layout, mint-pill doc count (TDD)e1c78e3frefactor:ReaderRecentDocscompact card-head, mint-pill "Neu" badge (TDD)e598f5a5refactor:ReaderRecentStoriescard-head link, touch targets (TDD)60326cfbrefactor:ReaderDraftsModulemint left-border, card-head, chevron (TDD)43d36c89feat: wireReaderHeaderBarinto+page.svelte, grid content-row, deleteReaderStatsStripVerification
src/lib/paraglide/directory (Vite can't delete root files during recompile); not caused by this PRDecision Queue resolutions applied
min-h-[44px]on all interactive regions throughouttext-brand-navy(token, dark-mode safe)