# Dokumente Dashboard · Final Design Spec

Redesign of the homepage (`/`) as an action-led document hub. Replaces the wall-of-lists approach with a warm, collaborative dashboard: personal greeting, hero "resume" card, Mission Control 3-up, Family Pulse (this week only), activity feed, and sidebar dropzone. Variant A selected from a three-concept exploration. Addresses Issue #271.

Final · Ready for implementation
Issue
#271 — Dashboard redesign
Variant
A — Resume + Family Pulse · hero-led, warm greeting, mission-control 3-up
Thumbnails
Hero only (large = legible). No thumbnails in task rows — kurrent is unreadable noise at small sizes.
Progress framing
"Diese Woche" only. Never show 1,500 total — demotivating at early state. Per-letter bars stay (clear denominator).
📐 Mockup scale notice — all font-size, height, and padding values in the mockup CSS are scaled to ~55% of actual implementation values. Do not copy sizes from mockup CSS. Use the ⚙ Implementation Reference tables after each section.
1 Full page — desktop (1280px)
Default — logged in as Marie
localhost:3000/
Hallo, Marie.
Klaus hat dich gestern in einem Brief erwähnt, und Anna wartet auf deine Meinung zur „Postkarte aus Wien".
Titel, Personen, Tags durchsuchen…
Filter
Weiter, wo du aufgehört hast Seite 2 von 4
Brief an Frieda aus dem Harz
Frieda Lehmann an Wilhelm Lehmann · Bad Sachsa, 14. März 1923
„Liebe Frieda, der Schnee ist endlich gewichen und die Wege wieder begehbar. Vater hat heute Morgen vom Markt geschrieben, daß die Preise abermals…"
Transkription 38% MR AN du und Anna
oder anderen Brief wählen
Mitmachen
Segmentieren5 offen
Seiten zu Briefen zuordnen.
Konvolut Rose, Heft III
Mappe A·14 Seiten
AN
Umschlag Mariahilf
19. Nov 1925·3 Seiten
Mappe B — unsortiert
neu, 18. Apr·27 Seiten
Briefbündel Lehrzeit
1928–1930·11 Seiten
Einzelseiten Dachboden
ohne Datum·8 Seiten
Alle anzeigen →
Transkribieren5 offen
Alte Handschriften lesen und abtippen.
Brief an Frieda aus dem Harz
14. März 1923·38%
MR
Postkarte aus Wien
2. Aug 1924·60%
OS
Geburtstagsbrief
22. Mai 1928·5%
LO
Weihnachtsgruß an die Familie
21. Dez 1931·12%
MR
Brief aus Hamburg
3. Juni 1929
Alle anzeigen →
Prüfen4 offen
Letzte Korrektur und freigeben.
Brief aus der Lehrzeit
8. Okt 1930·fertig
TH
Weihnachtsgruß
21. Dez 1931·fertig
MR
Geschäftsbrief Mariahilf
19. Nov 1925·fertig
KL
Kartengruß aus Dresden
7. Apr 1927·fertig
AN
Alle anzeigen →
Diese Woche · gemeinsam
Ihr habt zusammen 86 Seiten bearbeitet.
Du selbst hast 4 davon transkribiert.
AN KL OS MR LO 6 Mitwirkende
23
Briefe transkribiert
9
Briefe geprüft
47
Seiten hochgeladen
Kommentare & Aktivität Alle
AN
Anna hat transkribiert Postkarte aus Wien
vor 12 Min.
KL
Klaus hat dich erwähnt in Brief an Frieda für dich
vor 1 Std.
OS
Oskar hat zugeordnet Konvolut Rose, Heft III
vor 3 Std.
LO
Lotte hat geantwortet auf deinem Kommentar für dich
gestern
TH
Theo hat 4 Scans hochgeladen Mappe B
gestern
Hochladen
↑
Neue Scans hochladen
Hierher ziehen oder klicken
PDF, JPG, PNG bis 50 MB
Implementation Reference — Page structureroutes/+page.svelte + +page.server.ts

Element | Tailwind classes | Real px | Notes
--- | --- | --- | ---
Page background | `min-h-screen bg-canvas` | — | Canvas = `#f0efe9`
Main content wrapper | `max-w-screen-xl mx-auto px-8 py-8` | 32px padding, 1280px max | —
2-column layout grid | `grid grid-cols-[1fr_320px] gap-5 items-start` | 20px gap, 320px sidebar | Collapses to 1-col on mobile
Main column | `flex flex-col gap-5` | 20px gap between sections | —
Sidebar | `flex flex-col gap-5` | 20px gap | Sticky on tall viewports: `sticky top-[80px]` (below header)

2 Header — authenticated nav bar
Dokumente active
Active nav item: white text + 2px mint border-bottom. Upload button: ghost (transparent bg, 25% white border).
Upload button — hover
Hover: rgba(255,255,255,.12) bg tint on upload button.
Implementation Reference — Header (authenticated)routes/+layout.svelte (existing — minimal changes needed)

Element | Tailwind classes | Real px | Notes
--- | --- | --- | ---
Header | `sticky top-0 z-50 bg-[#012851]` | — | Already implemented — no change needed
Mint stripe | `h-1 bg-accent` | 4px | Already implemented
Nav bar height | `h-16 flex items-center px-8` | 64px | Already implemented
Upload button (new) | `inline-flex items-center gap-2 border border-white/25 text-white font-sans text-[11px] font-bold tracking-[.12em] uppercase px-3.5 py-1.5 rounded-sm hover:bg-white/10 transition-colors` | 11px font | New addition to existing header. Routes to `/documents/new` or triggers upload modal.
User avatar | `w-8 h-8 rounded-full bg-white text-ink font-sans text-[11px] font-bold flex items-center justify-center` | 32px | Shows user initials. Click → user menu (out of scope for this issue).

3 Greeting + search bar
Morning greeting (activity-aware)
Guten Morgen, Marie.
Klaus hat dich gestern in einem Brief erwähnt, und Anna wartet auf deine Meinung zur „Postkarte aus Wien".
Titel, Personen, Tags durchsuchen…
Filter
Greeting text changes by time of day: "Guten Morgen" (<11h), "Hallo" (11–18h), "Guten Abend" (>18h).
Search — focused state
Hallo, Marie.
Klaus hat dich gestern in einem Brief erwähnt.
Frieda Lehmann
Filter
Implementation Reference — Greeting + Searchroutes/+page.svelte

Element | Tailwind / implementation | Real px | Notes
--- | --- | --- | ---
Greeting wrapper | `mb-5` | 20px bottom margin | —
Greeting headline | `font-serif font-normal text-[32px] leading-tight text-ink` | 32px | Time-of-day: `getHours() < 11 → m.greeting_morning()`. Uses logged-in user's first name from `data.currentUser.firstName`.
Greeting subtitle | `font-serif text-[17px] text-ink-2 mt-2 leading-[1.45]` | 17px | Driven by actual notification data. Priority: @mention > reply > new scan. Links to document. Falls back to a neutral welcome if no recent activity.
Search bar card | `bg-surface border border-line shadow-sm rounded-sm p-5 mb-6 flex items-center gap-3` | 20px padding, 24px bottom margin | Same component as existing `SearchFilterBar.svelte` — no change needed.
Search input | `flex-1 border border-line px-3.5 py-3 font-serif text-base text-ink placeholder:text-ink-3 rounded-none focus-visible:ring-2 focus-visible:ring-focus-ring/15 focus-visible:border-ink` | 16px font / 48px tall | Already implemented in SearchFilterBar.

4 Hero — "Weiter, wo du aufgehört hast"
Default — in-progress letter
Weiter, wo du aufgehört hast Seite 2 von 4
Brief an Frieda aus dem Harz
Frieda Lehmann an Wilhelm Lehmann · Bad Sachsa, 14. März 1923
„Liebe Frieda, der Schnee ist endlich gewichen und die Wege wieder begehbar. Vater hat heute Morgen geschrieben…"
Transkription 38% MR AN du und Anna
oder anderen Brief wählen
Thumbnail only here — 180×246px on real page. This is the only location large enough to make kurrent legible.
No in-progress letter (empty state)
✉
Wo möchtest du anfangen?
Wähle einen Brief aus einer der Warteschlangen unten oder durchsuche das Archiv.
Shown when the user has no in-progress transcription task.
Implementation Reference — Hero resume cardlib/components/DashboardResumeStrip.svelte (refactor from existing)

Element | Tailwind classes | Real px | Notes
--- | --- | --- | ---
Hero card | `bg-surface border border-line shadow-sm rounded-sm p-7 grid grid-cols-[180px_1fr] gap-8 items-center` | 28px padding, 32px gap | Collapses to single-column on mobile: `sm:grid-cols-[180px_1fr]`
Letter thumbnail | `w-[180px] h-[246px] rounded-sm shadow-sm flex-shrink-0` | 180×246px | Real scan image when available; SVG placeholder when not. `<img>` with `object-cover`.
Eyebrow label | `inline-flex items-center gap-2 font-sans text-[11px] font-bold tracking-[.18em] uppercase text-turquoise` | 11px | Dot: `w-2 h-2 rounded-full bg-turquoise`. i18n: `m.dashboard_resume_label()`
Page counter | `font-sans text-[11px] tracking-[.12em] uppercase text-ink-3` | 11px | `m.dashboard_page_of({ page, pages })`
Letter title | `font-serif font-normal text-[30px] leading-tight text-ink mb-1.5` | 30px | From `document.title`
Archival caption | `font-serif text-sm italic text-ink-3 mb-4` | 14px | "{from} an {to} · {place}, {date}"
Excerpt pull-quote | `font-serif text-[17px] italic leading-[1.55] text-ink-2 border-l-[3px] border-accent pl-4 mb-5` | 17px, 3px left border (mint) | First 200 chars of transcription draft or OCR excerpt.
Progress row | `flex justify-between items-center font-sans text-xs text-ink-3 mb-2` | 12px | Left: "{pct}% transkribiert". Right: avatar stack + collaborator names.
Progress bar | `w-full h-1.5 bg-[#eeede8] rounded-full overflow-hidden mb-5` | 6px | Fill: `bg-turquoise` (`#00c7b1`). This is per-letter (clear denominator) — motivating, not crushing.
Primary CTA | `bg-primary text-primary-fg font-sans text-xs font-bold tracking-[.12em] uppercase px-5 py-3 rounded-sm inline-flex items-center gap-2.5` | 12px | href="/documents/{id}/edit". i18n: `m.dashboard_resume_cta()`
"oder anderen Brief" link | `font-sans text-xs font-semibold text-ink-2 underline decoration-[#cdcbbf] underline-offset-[4px]` | 12px | href="/" (to search). Not a button — no prominent treatment.
Empty state | Centered card with envelope icon, serif heading, body, CTA button | — | Shown when backend returns no active transcription for current user.

5 Mission Control — 3-column task queues
Segmentieren — row states
Segmentieren5 offen
Seiten zu Briefen zuordnen.
Konvolut Rose, Heft III
Mappe A·14 Seiten
AN
Umschlag Mariahilf
19. Nov 1925·3 Seiten
Mappe B — unsortiert
neu, 18. Apr·27 Seiten
Alle anzeigen →
Row with sienna tint = hover state. Filled avatar = someone has started it. Dashed circle = nobody started yet.
Transkribieren — with % progress
Transkribieren5 offen
Alte Handschriften lesen und abtippen.
Brief an Frieda aus dem Harz
14. März 1923·38%
MR
Postkarte aus Wien
2. Aug 1924·60%
OS
Geburtstagsbrief
22. Mai 1928·5%
LO
Brief aus Hamburg
3. Juni 1929
Alle anzeigen →
Percentage shown inline in metadata for in-progress items. No progress bar in rows — that would be too heavy.
Prüfen — all complete
Prüfen4 offen
Letzte Korrektur und freigeben.
Brief aus der Lehrzeit
8. Okt 1930·fertig ✓
TH
Weihnachtsgruß
21. Dez 1931·fertig ✓
MR
Geschäftsbrief Mariahilf
19. Nov 1925·fertig ✓
KL
Alle anzeigen →
Green "fertig" label when pct === 100. Still shows in queue until someone actively reviews and archives.
Implementation Reference — Mission Controllib/components/MissionControlStrip.svelte (refactor from existing)

Element | Tailwind classes | Real px | Notes
--- | --- | --- | ---
3-column grid | `grid grid-cols-3 gap-4` | 16px gap | Collapses to 1-col on mobile
Column card | `bg-surface border border-line rounded-sm overflow-hidden flex flex-col` | — | —
Column header — Segmentieren | `p-4 border-b border-[#eeede8] bg-[rgba(160,82,45,.06)]` | 16px padding | Sienna tint
Column header — Transkribieren | `bg-[rgba(0,199,177,.08)]` | — | Turquoise tint
Column header — Prüfen | `bg-[rgba(90,138,106,.08)]` | — | Sage tint
Column label | `font-sans text-[12px] font-bold tracking-[.16em] uppercase text-ink` | 12px | i18n: `m.queue_segment()`, `m.queue_transcribe()`, `m.queue_review()`
Open count | `font-sans text-[11px] text-ink-3` | 11px | "{n} offen"
Column blurb | `font-serif text-[13px] text-ink-2 leading-snug mt-1.5` | 13px | i18n keys: `m.queue_segment_blurb()` etc.
Task row wrapper | `p-1.5` | 6px | Padding around all rows within the column body.
Task row | `<a> flex items-center gap-3 px-3.5 py-2.5 rounded-sm hover:bg-[#f7f6f1] transition-colors no-underline` | 14px/10px padding | Entire row is a link to the document edit page.
Task title | `font-serif text-base text-ink truncate leading-snug` | 16px | Single line, truncated with ellipsis.
Task metadata | `font-sans text-[12px] text-ink-3 mt-0.5 flex items-center gap-2` | 12px | Hint · separator · pages or % or "fertig". "fertig" = `text-[#5a8a6a] font-semibold`.
Starter avatar (filled) | `w-[22px] h-[22px] rounded-full flex-shrink-0` | 22px | Uses person's `color` from the persons list.
Starter avatar (empty) | `w-[22px] h-[22px] rounded-full border-[1.5px] border-dashed border-[#cdcbbf] flex-shrink-0` | 22px | Title attribute: "noch niemand angefangen" (tooltip).
"Alle anzeigen" footer | `text-center py-2.5 font-sans text-[11px] font-bold tracking-[.12em] uppercase text-ink border-t border-[#eeede8] mt-auto block` | 11px | Links to filtered document list for that queue.

6 Family Pulse — sidebar "Diese Woche"
Normal week
Diese Woche · gemeinsam
Ihr habt zusammen 86 Seiten bearbeitet.
Du selbst hast 4 davon transkribiert.
AN KL OS MR LO TH 6 Mitwirkende
23
Briefe transkribiert
9
Briefe geprüft
47
Seiten hochgeladen
Quiet week (low numbers)
Diese Woche · gemeinsam
Ihr habt zusammen 4 Seiten bearbeitet.
Du selbst hast 1 davon transkribiert.
AN MR 2 Mitwirkende diese Woche
2
Briefe transkribiert
0
Briefe geprüft
2
Seiten hochgeladen
Low numbers are fine — no framing around "goal". The number grows; no ceiling. Never shows 1,500 total.
Implementation Reference — Family Pulselib/components/ (new component) — DashboardFamilyPulse.svelte

Element | Tailwind classes | Real px | Notes
--- | --- | --- | ---
Card | `bg-surface border border-line shadow-sm rounded-sm p-6` | 24px padding | White card — intentionally same as other sidebar cards (no warm cream — confirmed decision).
Eyebrow | `font-sans text-[11px] font-bold tracking-[.18em] uppercase text-ink-3 mb-2` | 11px | i18n: `m.pulse_eyebrow()` = "Diese Woche · gemeinsam"
Headline | `font-serif text-[22px] font-normal leading-snug text-ink mb-1` | 22px | i18n: `m.pulse_headline({ pages })`. Strong tag on the number: `font-semibold`.
Personal contribution | `font-serif text-[14px] text-ink-3 mb-4` | 14px | i18n: `m.pulse_you({ pages })`. Hidden when current user's contribution = 0.
Avatar stack | `inline-flex items-center` with `-ml-2` on all except first | 28px avatars, -8px overlap | Shows contributors who participated this week. `ring-2 ring-white` on each.
Contributor count | `font-sans text-xs text-ink-3 ml-3` | 12px | "{n} Mitwirkende"
3-stat grid | `grid grid-cols-3 gap-2.5 mt-4` | 10px gap, 16px top margin | —
Stat card | `bg-[#faf9f4] border border-[#eeede8] rounded-sm p-3.5` | 14px padding | —
Big number | `font-serif text-[30px] font-normal text-ink leading-none flex items-baseline gap-2` | 30px | Dot: `w-1.5 h-1.5 rounded-full flex-shrink-0` in kind color.
Stat label | `font-sans text-[12px] text-ink-3 mt-1.5` | 12px | i18n: `m.pulse_transcribed()`, `m.pulse_reviewed()`, `m.pulse_uploaded()`
Data source | — | — | New backend endpoint: `GET /api/dashboard/pulse?period=week`. Returns `{ transcribed, reviewed, uploaded, pages, you: { pages }, contributors: Person[] }`.

7 Activity feed & sidebar dropzone
Activity feed
Kommentare & Aktivität Alle
AN
Anna hat transkribiert Postkarte aus Wien
vor 12 Min.
KL
Klaus hat dich erwähnt in Brief an Frieda für dich
vor 1 Std.
LO
Lotte hat geantwortet auf deinem Kommentar für dich
gestern
Tinted dot = activity kind (turquoise = transcribe, pink = mention, purple = reply, sienna = segment, blue = upload). "für dich" badge on @mentions and replies.
Dropzone — idle
Hochladen
↑
Neue Scans hochladen
Hierher ziehen oder klicken
PDF, JPG, PNG bis 50 MB
Dropzone — drag active
Hochladen
↑
Loslassen zum Hochladen
PDF, JPG, PNG bis 50 MB
Drag-over: navy border, mint tint bg, title changes to "Loslassen zum Hochladen".
Implementation Reference — Activity feed & dropzoneroutes/+page.svelte (new feed) + routes/DropZone.svelte (existing, minor tweaks)

Element | Tailwind classes | Real px | Notes
--- | --- | --- | ---
Feed card wrapper | `bg-surface border border-line shadow-sm rounded-sm p-5` | 20px padding | —
Feed item | `flex gap-3.5 items-start py-3.5 border-b border-[#eeede8] last:border-b-0` | 14px padding, 14px gap | —
Feed avatar | `w-9 h-9 rounded-full flex-shrink-0` | 36px | Color from person's `color` field.
Feed text | `font-serif text-[15px] leading-snug text-ink` | 15px | Name: `font-sans text-[13px] font-bold`. Verb: `text-ink-2`. Target: link `text-ink underline underline-offset-[2px]`.
"für dich" badge | `ml-2 inline-block px-2 py-px border border-accent rounded-full font-sans text-[10px] font-bold tracking-[.12em] uppercase text-ink bg-accent/20 align-middle` | 10px | Only shown when `activity.you === true` (mention or reply to current user).
Timestamp row | `mt-1 inline-flex items-center gap-2 font-sans text-[11px] text-ink-3` | 11px | Kind dot: `w-1.5 h-1.5 rounded-full` in kind color (see dot-color map below).
Kind → dot color | transcribe: `#00c7b1` · segment: `#a0522d` · review: `#5a8a6a` · upload: `#3060b0` · mention: `#c0446e` · reply: `#7a4f9a` · tag: `#c17a00` | — | Purely decorative — color-blind safe because avatar + name already identify the person and action.
Dropzone — idle | `flex flex-col items-center gap-2.5 py-6 px-4 border border-dashed border-ink/30 rounded-sm text-center transition-all` | — | Already implemented in `DropZone.svelte`. Already existing; verify styles match.
Dropzone — drag active | + `border-ink bg-accent/18` | — | Title changes to `m.dropzone_release()`.
Data source — feed | — | — | New endpoint: `GET /api/dashboard/activity?limit=7`. Returns recent actions across all document activity for the archive.

8 Implementation notes — backend, i18n, data model
New backend endpoints
GET /api/dashboard/*

Endpoint | Response | Notes
--- | --- | ---
`GET /api/dashboard/resume` | `{ document, page, pages, pct, collaborators: Person[] }` | The current user's last-touched in-progress transcription. Returns `null` when none.
`GET /api/dashboard/pulse?period=week` | `{ transcribed, reviewed, uploaded, pages, you: { pages, comments }, contributors: Person[] }` | Aggregate stats for the current ISO week. Never exposes cumulative total.
`GET /api/dashboard/activity?limit=7` | `Activity[]` — `{ who: Person, verb, target, targetId, when, kind, youMentioned }` | Recent archive-wide activity, newest first. `youMentioned=true` when activity targets the current user.

Existing endpoints used

Endpoint | Used for
--- | ---
`GET /api/documents?status=UPLOADED&transcriptionPct=0..99&sort=DATE&limit=5` | Transkribieren queue
`GET /api/documents?status=PLACEHOLDER&limit=5` | Segmentieren queue
`GET /api/documents?status=TRANSCRIBED&limit=5` | Prüfen queue
`GET /api/documents?q=&sort=DATE&dir=desc` | Search bar (unchanged)

New i18n keys
messages/de.json additions

Key | DE
--- | ---
`greeting_morning` | Guten Morgen, {name}.
`greeting_day` | Hallo, {name}.
`greeting_evening` | Guten Abend, {name}.
`dashboard_resume_label` | Weiter, wo du aufgehört hast
`dashboard_page_of` | Seite {page} von {pages}
`dashboard_resume_cta` | Weitertranskribieren
`dashboard_resume_other` | oder anderen Brief wählen
`dashboard_empty_title` | Wo möchtest du anfangen?
`dashboard_empty_body` | Wähle einen Brief aus einer Warteschlange unten oder durchsuche das Archiv.
`dashboard_mission_caption` | Mitmachen
`queue_segment` | Segmentieren
`queue_segment_blurb` | Seiten zu Briefen zuordnen.
`queue_transcribe` | Transkribieren
`queue_transcribe_blurb` | Alte Handschriften lesen und abtippen.
`queue_review` | Prüfen
`queue_review_blurb` | Letzte Korrektur und freigeben.
`queue_n_open` | {n} offen
`queue_show_all` | Alle anzeigen →
`pulse_eyebrow` | Diese Woche · gemeinsam
`pulse_headline` | Ihr habt zusammen {pages} Seiten bearbeitet.
`pulse_you` | Du selbst hast {pages} davon transkribiert.
`pulse_contributors` | {n} Mitwirkende
`pulse_transcribed` | Briefe transkribiert
`pulse_reviewed` | Briefe geprüft
`pulse_uploaded` | Seiten hochgeladen
`feed_caption` | Kommentare & Aktivität
`feed_for_you` | für dich
`dropzone_release` | Loslassen zum Hochladen

Implementation sequence:

- 1. Add `GET /api/dashboard/resume`, `/pulse`, `/activity` endpoints to Spring Boot
- 2. Regenerate TypeScript types (`npm run generate:api`)
- 3. Update `routes/+page.server.ts` to load all dashboard data in parallel
- 4. Refactor `DashboardResumeStrip.svelte` — add thumb, caption, pull-quote, progress bar, new CTAs
- 5. Refactor `MissionControlStrip.svelte` — 3-col grid, TaskRow with avatar-of-starter, accent headers
- 6. Create `DashboardFamilyPulse.svelte` (new)
- 7. Create `DashboardActivityFeed.svelte` (new) — replaces DashboardRecentDocuments
- 8. Add greeting headline + subtitle to `+page.svelte`
- 9. Add Upload button to `+layout.svelte` header
- 10. Add all i18n keys to de/en/es message files