Person Detail — Korrespondenz-Überblick

Final design for the dashboard block that extends /persons/[id]. Gives every person page a correspondence-at-a-glance view — stats, activity per year, direction split, top correspondents, top locations, tag cloud — and turns each element into a filter shortcut into /briefwechsel. Replaces the current CoCorrespondentsList block.

FINAL
Route
/persons/[id] · right column
Layout
35% / 65% split · stacks below 768 px
Sections shown
Stats · Histogram · Direction · Correspondents · Locations · Tags
Deep links
Every tile → /briefwechsel with filters applied
Reading this spec. Mockups in Section 02 are scaled to ~55 % of real pixel values so that multiple viewports fit on one page. Never copy pixel sizes from the mockups. Use the impl-ref tables for exact Tailwind class + pixel value. Close-ups in Section 03 render each dashboard block at ~100 % scale for pixel-accurate reference.
Inhalt
  1. 01 Page anatomy default · 1440 px
  2. 02 Content states × 3 viewports 4 states · 12 frames
  3. 03 Dashboard block close-ups 6 blocks @ real size
  4. 04 Deep-link grammar every tile → /briefwechsel
  5. 05 Accessibility contract WCAG AA/AAA
  6. 06 Implementation notes backend API · components

01Page Anatomy — Default State at 1440 px

The page is a 35% / 65% split (existing lg:grid-cols-[35%_65%]). Left column keeps PersonCard and NameHistoryCard. Right column replaces CoCorrespondentsList with the new PersonDashboard block at the top, followed by the existing sent/received document lists.

familienarchiv.de/persons/{id}
DokumentePersonenBriefwechselChronik
← Zurück
WG
Walter de Gruyter
1862 – 1923
Bearbeiten
Briefwechsel

Korrespondenz-Überblick

↗ Briefwechsel
851
gesamt
612
ausgehend
239
eingehend
42
Jahre

Aktivität über die Jahre Spitzenjahr 1922 · 78

189819221940

Richtungsverteilung

→ 612 · 72%← 239 · 28%

Top Korrespondenten von 87

Walter Dieckmann184
Herbert Cram143
Ella Dieckmann88
Eugenie de Gruyter77
Gertrud v. Rofden58

Top Orte von 42

Berlin412
B.Lichterfelde180
Bad Kissingen58
Cöln37
Belgard26

Beliebte Schlagwörter Klick filtert den Briefwechsel

VerlagFamilieGeburtstagWeihnachtenKuraufenthaltReiseGeschäftKriegKrankheitSchuleHochzeitNeujahr
A · Person card
Unchanged in this spec. Avatar, full name, lifespan, bearbeiten + Briefwechsel buttons. The primary action moves from "Edit" to "Open Briefwechsel" because that is what users actually do next.
B · Korrespondenz-Überblick
New dashboard block. Dark navy header strip with "Briefwechsel öffnen" CTA on the right. Stats strip below header, then 4 sections separated by 1 px rules.
Stats strip
4 cells at desktop / 2×2 at tablet / 4 stacked at mobile. Numbers in serif (Merriweather Black) at 22 px. Direction colours match row border colours elsewhere (out = navy, in = accent).
Activity histogram
One bar per year in the range (1898 → 1940 = 43 bars). Peak bar uses bg-primary, others bg-accent/60. Hovering a bar shows "{year} · {count} Briefe" tooltip; clicking filters /briefwechsel?senderId=…&from=YYYY-01-01&to=YYYY-12-31.
Top correspondents
Up to 6 rows. Name + proportional bar + count. Click opens /briefwechsel?senderId=<this>&receiverId=<other> (bilateral view). "Alle N Korrespondenten →" link below.
Top locations & Tag cloud
Location tiles mirror correspondents. Tag cloud sized by frequency — xl > 100, l > 50, m > 20, muted ≤ 20. Click on any tag: /briefwechsel?senderId=<this>&tag=<id>.
Implementation Reference — Page Shellextends existing /persons/[id]
ElementClassesRealNote
Page containermx-auto max-w-6xl px-4 py-10max 72remUnchanged · existing route shell
2-column gridlg:grid lg:grid-cols-[35%_65%] lg:gap-832 px gapExisting · unchanged
Left column stackPersonCard → NameHistoryCard (mt-6)24 px mtExisting · unchanged
Right columnPersonDashboard → PersonDocumentList(sent) → PersonDocumentList(received)new + existingPersonDashboard replaces CoCorrespondentsList
Dashboard containeroverflow-hidden rounded-sm border border-line bg-surface shadow-sm1 px borderMatches card pattern from CLAUDE.md
Dashboard headerflex items-center justify-between gap-3 bg-primary text-primary-fg px-5 py-312 px y paddingDark navy strip — sets dashboard apart from body card patterns
Dashboard titlefont-serif text-base font-bold16 px / 700Merriweather
"Briefwechsel öffnen" CTAbg-accent text-primary text-xs font-extrabold uppercase tracking-wide px-3 py-1.5 rounded-sm min-h-[44px] min-w-[44px] inline-flex items-center44 px minWCAG 2.2 AA touch target; Paraglide m.person_open_conversation()

02Content States × 3 Viewports

Four states. Every frame renders the page shell (header → back link → split grid). Reading order per state: 320 px → 768 px → 1440 px. At 320 and 768, the grid stacks and the dashboard flows below the person card.

01Default · Full dataset (851 letters · 42 years · 87 correspondents)
The happy path. Every section has data. Numbers are formatted with German number formatting; year range in histogram auto-fits to data span; top lists truncate to 5 on tablet, 6 on desktop, 3 on mobile.
320 px · Mobile176 px @ 55%
9:41
Personen
← Zurück
WG
Walter de Gruyter
1862 – 1923
Briefwechsel

Überblick

851
ges.
612
239
42J
Jahre

Aktivität

Top

W. Dieckmann184
H. Cram143
E. Dieckmann88

Schlagwörter

VerlagFamilieGeburtstagKur
768 px · Tablet422 px @ 55%
…/persons/9ed8…
PersonenBriefwechsel
← Zurück
WG
Walter de Gruyter
1862 – 1923
Edit
Briefwechsel

Korrespondenz-Überblick

↗ Öffnen
851
gesamt
612
ausgehend
239
eingehend
42
Jahre

Aktivität 1922 · 78

189819221940

Richtung

→ 612 · 72%← 239 · 28%

Top Korresp.

W. Dieckmann184
H. Cram143
E. Dieckmann88
E. de Gruyter77
G. Rofden58

Top Orte

Berlin412
B.Lichterfelde180
Bad Kissingen58
Cöln37

Schlagwörter

VerlagFamilieGeburtstagWeihnachtenKurReiseKriegKrankheitTod
1440 px · Desktop780 px @ 55%
familienarchiv.de/persons/9ed8fd47-…
DokumentePersonenBriefwechselChronik
← Zurück
WG
Walter de Gruyter
1862 – 1923
Bearbeiten
Briefwechsel

Korrespondenz-Überblick

↗ Briefwechsel öffnen
851
Briefe gesamt
612
ausgehend
239
eingehend
42
Jahre

Aktivität über die Jahre Spitzenjahr 1922 · 78 Briefe

189819221940

Richtungsverteilung

→ 612 ausgehend · 72%← 239 eingehend · 28%

Top Korrespondenten 6 von 87

Walter Dieckmann184
Herbert Cram143
Ella Dieckmann88
Eugenie de Gruyter77
Gertrud von Rofden58
Käthe Dieckmann47

Top Orte 5 von 42

Berlin412
B.Lichterfelde180
Bad Kissingen58
Cöln37
Belgard26

Beliebte Schlagwörter Klick filtert den Briefwechsel

VerlagFamilieGeburtstagWeihnachtenKuraufenthaltReiseGeschäftKriegKrankheitSchuleHochzeitNeujahr
Wrap behaviour by viewport.
  • 320 px: grid stacks. Person card then dashboard. Stats drop to 2×2 cells. Histogram shows aggregated year-groups (6 bars), not individual years. Top lists truncate to 3. Location section collapses.
  • 768 px: grid stacks. 4×1 stats. Full histogram. Two-col section shows correspondents & locations side-by-side. Tag cloud shows 8–9 tags.
  • 1440 px: 35/65 split. All sections fully rendered. Tag cloud shows all significant tags (those with count ≥ 5) plus up to 5 muted smaller ones.
02Empty · Person has no letters yet
Person exists (imported, created manually) but no documents reference them as sender or receiver. Dashboard collapses to a single reassurance card inviting the user to upload.
320 px · Mobile176 px @ 55%
9:41
← Zurück
NN
Neue Person

Überblick

Noch keine Briefe
Diese Person hat noch keine Korrespondenz im Archiv.
768 px · Tablet422 px @ 55%
…/persons/new-id
Personen
← Zurück
NN
Neue Person
Bearbeiten

Korrespondenz-Überblick

Noch keine Briefe
Diese Person hat noch keine Korrespondenz im Archiv. Sobald ein Brief zugewiesen wird, erscheint der Überblick hier automatisch.
1440 px · Desktop780 px @ 55%
familienarchiv.de/persons/new-id
PersonenBriefwechsel
← Zurück
NN
Neue Person
Bearbeiten

Korrespondenz-Überblick

Noch keine Briefe
Diese Person hat noch keine Korrespondenz im Archiv. Sobald ein Brief als Absender oder Empfänger zugewiesen wird, erscheint der Überblick hier automatisch.
03Sparse · Few letters (< 10)
Person has between 1 and 9 letters. Histogram would be uninformative; it collapses to a single line with year range. Top lists show whatever is present — no "von N" note. No tag cloud unless at least 3 different tags exist.
320 px · Mobile176 px @ 55%
9:41
← Zurück
EB
Elsbeth Brandt
1890 – 1963

Überblick

7
ges.
4
3
3J
Jahre

Zeitraum

1919 – 1922

Top

W. de Gruyter4
H. Cram3
768 px · Tablet422 px @ 55%
…/persons/elsbeth
← Zurück
EB
Elsbeth Brandt
1890 – 1963
Briefwechsel

Korrespondenz-Überblick

↗ Öffnen
7
gesamt
4
ausgehend
3
eingehend
3
Jahre

Zeitraum

1919 · erster Brief1922 · letzter Brief

Korrespondenten

Walter de Gruyter4
Herbert Cram3
1440 px · Desktop780 px @ 55%
familienarchiv.de/persons/elsbeth
Personen
← Zurück
EB
Elsbeth Brandt
1890 – 1963
Bearbeiten
Briefwechsel

Korrespondenz-Überblick

↗ Briefwechsel öffnen
7
Briefe gesamt
4
ausgehend
3
eingehend
3
Jahre

Zeitraum

1919 · erster Brief1922 · letzter Brief

Korrespondenten

Walter de Gruyter4
Herbert Cram3

Orte

B.Lichterfelde5
Berlin2
Sparse-mode simplifications.
  • Histogram replaced with a single "Zeitraum" line showing first and last letter years. Histogram returns as soon as letterCount >= 10 and yearSpan >= 3.
  • Tag cloud hidden when fewer than 3 distinct tags across all letters. No "empty tag cloud" placeholder.
  • "Top of N" notes dropped — numbers speak for themselves.
04Loading · Skeleton
While the aggregation endpoint is in-flight. Person card renders from the /api/persons/{id} payload (fast); the dashboard shows skeleton rectangles for each section in the same grid slots.
320 px · Mobile176 px @ 55%
9:41
← Zurück
WG
Walter de Gruyter
1862 – 1923

Überblick

768 px · Tablet422 px @ 55%
…/persons/…
← Zurück
WG
Walter de Gruyter
1862 – 1923
Edit
Briefwechsel

Korrespondenz-Überblick

1440 px · Desktop780 px @ 55%
familienarchiv.de/persons/…
← Zurück
WG
Walter de Gruyter
1862 – 1923

Korrespondenz-Überblick

03Dashboard Block Close-Ups · ~100% Scale

Six blocks rendered at near-real pixel sizes. These are the reference renderings developers check against when implementing PersonDashboard.svelte and its sub-components.

Block A · Stats strip
851
Briefe gesamt
612
ausgehend
239
eingehend
42
Jahre
Block A — Stats StripDesktop = 4 cells · Tablet = 4 cells · Mobile = 2×2
PartClassesRealNote
Strip containergrid grid-cols-2 sm:grid-cols-4 gap-px bg-line border-b border-line1 px gap (shows as lines)Separators are the background showing through
Cellbg-muted px-4 py-3.5 text-center14 px y paddingUses bg-muted not bg-surface so separators read
Numberfont-serif text-[22px] font-black text-primary leading-none tabular-nums tracking-tight22 px / 900Merriweather Black · .out = primary, .in = accent
Labelmt-1 text-[10px] font-bold uppercase tracking-wide text-ink-310 pxDirection labels match number colour
Mobile formatterAbbreviate "Briefe gesamt" → "gesamt"m.person_stats_total_short()Paraglide key with _short suffix
Block B · Activity histogram (one bar per year)
18981922 ▲ Spitzenjahr · 78 Briefe1940
Block B — Activity Histogramdeep-links via from/to params
PartClassesRealNote
Containerflex items-end gap-0.5 h-[72px] pt-172 px heightHeight tuned to comfortable reading — not too small
Bar (normal)flex-1 bg-accent/60 rounded-t-sm hover:bg-accent/90 transition-colors cursor-pointervariable widthMin-width 3 px for ≤ 30 years; thinner for longer ranges
Bar (peak)flex-1 bg-primary rounded-t-smmax heightExactly one peak bar highlighted
Bar link<a href="/briefwechsel?senderId={id}&from={year}-01-01&to={year}-12-31">Whole bar is the link; tooltip announces "Jahr {year} · {count} Briefe"
Year labelsflex justify-between text-[10px] font-bold text-ink-3 mt-1.510 pxOnly show: earliest year · peak year · latest year
Tooltipnative title attribute on bar"1922 · 78 Briefe" — Paraglide pluralized
Empty-year barsrendered as min-height: 2px placeholder2 pxKeeps bar spacing regular across decades with gaps
Block C · Direction split (same component as /briefwechsel distribution bar, re-used)
→ 612 ausgehend · 72%← 239 eingehend · 28%
Re-use, do not duplicate. The bilateral DistributionBar.svelte component from the thumbnail rows spec is also used here. Same props (outCount, outLabel, inCount, inLabel), same aria-label pattern.
Block D · Top correspondents
Walter Dieckmann184
Herbert Cram143
Ella Dieckmann88
Eugenie de Gruyter77
Gertrud von Rofden58
Käthe Dieckmann47
Block D — Top List (correspondents & locations share this)rendered as <ol> for semantic ranking
PartClassesRealNote
Wrapper<ol> · flex flex-col gap-28 px gapOrdered list — rank order matters, screen readers announce "1 of 6"
Item<li> containing <a> · flex items-center gap-3 text-sm px-1.5 py-1 rounded-sm min-h-[32px] hover:bg-muted32 px minNot 44 px because these are secondary links inside a card — desktop focus. Mobile bumps to 44 via md:min-h-[32px] min-h-[44px]
Nameflex-1 font-semibold text-ink truncate14 px / 600Truncate middle-ellipsis on very long German names at < 768 px
Proportional bar wrapperw-[120px] h-[7px] bg-line rounded overflow-hidden shrink-0 hidden sm:block120 × 7Hidden on mobile — the number carries the data
Proportional bar fillh-full bg-primary roundedwidth = value ÷ maxWidths computed client-side from the list's max value
Countw-10 text-right text-sm text-ink-3 font-bold tabular-nums shrink-014 px / 700Tabular figures so columns align
"Alle N anzeigen →"mt-2.5 text-xs font-bold text-primary border-b border-dashed border-primary/60 hover:border-primary12 px / 700Appears when total > shown count
Block E · Top locations — identical component to Block D
📍 Berlin412
📍 B.Lichterfelde180
📍 Bad Kissingen58
📍 Cöln37
📍 Belgard26
Emoji pin is decorative. Rendered via ::before with aria-hidden="true". The location string alone carries semantic meaning. Future iteration may replace with a map icon component.
Block F · Tag cloud — frequency-sized
VerlagFamilieGeburtstagWeihnachtenKuraufenthaltReiseGeschäftKriegKrankheitSchuleHochzeitNeujahr
Block F — Tag Cloudsize buckets map to count thresholds
SizeClassesCount thresholdNote
xltext-[15px] font-bold px-3.5 py-1 bg-accent text-primary rounded-full≥ 100Max 2 tags at this size — visual anchor
ltext-[13px] font-bold px-3 py-1 bg-accent text-primary rounded-full50–99Up to 4 tags
mtext-xs font-bold px-2.5 py-0.5 bg-accent text-primary rounded-full20–49Up to 6 tags
regulartext-xs font-bold px-2.5 py-0.5 bg-accent text-primary rounded-full5–19All tags meeting threshold
mutedtext-xs font-semibold px-2.5 py-0.5 bg-line text-ink-3 rounded-full< 5Up to 8 muted tags, sorted alphabetically
Click targetmin-h-[28px] min-w-[44px] inline-flex items-center44 px min widthVery short tag names ("Kur") still meet touch target
Hoverhover:-translate-y-px transition-transform1 px liftBypassed when prefers-reduced-motion

04Deep-Link Grammar — every tile → /briefwechsel

The dashboard is the discovery surface; /briefwechsel is the reading surface. Every clickable element in the dashboard adds filters to the existing /briefwechsel query string so the transition feels continuous.

Link grammarreplace placeholders with real values
ElementLinkNote
Header "↗ Briefwechsel öffnen"/briefwechsel?senderId={id}&dir=DESCOpens all letters for this person · no date filter · newest first
Stats — "gesamt"Same as headerEntire count is tap-to-open
Stats — "ausgehend"/briefwechsel?senderId={id}&direction=OUT&dir=DESCRequires new direction query param on /briefwechsel
Stats — "eingehend"/briefwechsel?senderId={id}&direction=IN&dir=DESCSame
Histogram bar/briefwechsel?senderId={id}&from={year}-01-01&to={year}-12-31Opens bilateral or single view scoped to that year
Top correspondent row/briefwechsel?senderId={id}&receiverId={otherId}&dir=DESCOpens the bilateral view for the pair
Top location row/briefwechsel?senderId={id}&location={locSlug}Requires new location query param on /briefwechsel
Tag chip/briefwechsel?senderId={id}&tagId={tagId}Requires new tagId query param on /briefwechsel
New query parameters required on /briefwechsel.
  • direction=OUT|IN — filter to letters in one direction only (existing endpoint already distinguishes sender vs receiver; this adds symmetry for the single-person view).
  • location=<slug> — case-insensitive match on Document.location. Slug because German locations can contain spaces and dots ("B.Lichterfelde", "Bad Kissingen").
  • tagId=<uuid> — filter to letters that reference this tag. If omitted, no tag filter is applied.

05Accessibility Contract · WCAG AA/AAA

Every colour pair on the dashboard has been measured. Semantics matter as much as colour: the stats use real numbers (not SVG), the histogram has tooltip text, the top lists are ordered lists, the tag cloud is a list of links.

Light Mode — Contrast Verificationlayout.css tokens
PairValueRatioWCAG
Stat number (primary on muted)#002850 on #f7f5f214.0:1AAA ✓
Stat label (ink-3 on muted)#666666 on #f7f5f25.4:1AA ✓
Stat "in" (accent on muted)#2F9E95 on #f7f5f24.5:1AA ✓ (borderline — number is 22 px / 900 qualifies as large)
Dashboard header title (surface on primary)#ffffff on #00285014.5:1AAA ✓
CTA "Briefwechsel öffnen" (primary on accent)#002850 on #a6dad88.1:1AAA ✓
Histogram bar (accent/60 on surface)rgba(47,158,149,.6) on #ffffff2.8:1Decorative (bars have titles; not text)
Histogram peak bar (primary on surface)#002850 on #ffffff14.5:1AAA ✓
Tag chip (primary on accent)#002850 on #a6dad88.1:1AAA ✓
Muted tag (ink-3 on line)#666666 on #eee8dc5.1:1AA ✓
Focus ring (primary on surface, 2 px offset)#00285014.5:1AAA ✓
Dark Mode — Contrast Verificationdata-theme="dark"
PairValueRatioWCAG
Stat number (ink on canvas-2)#f0efe9 on #01152615.1:1AAA ✓
Stat "out" (mint on canvas-2)#a1dcd8 on #0115269.6:1AAA ✓
Stat "in" (turquoise on canvas-2)#00c7b1 on #0115266.8:1AA ✓
Stat label (ink-3 on canvas-2)#8b97a5 on #0115267.1:1AAA ✓
Dashboard header (ink on navy-2)#f0efe9 on #01223f13.8:1AAA ✓
CTA (primary on mint)#012851 on #a1dcd89.6:1AAA ✓
Histogram peak (mint on canvas)#a1dcd8 on #010e1e9.2:1AAA ✓
Tag (turquoise on tint)#00c7b1 on rgba(0,199,177,.2)6.3:1AA ✓
Non-negotiable accessibility rules.
  • Stats are real <dl> / <dt> / <dd> pairs. Screen readers announce "Briefe gesamt: 851, ausgehend: 612, …".
  • Histogram wrapped in role="img" with aria-label="Aktivität über 42 Jahre, Spitzenjahr 1922 mit 78 Briefen". Each bar has a title attribute for sighted tooltip.
  • Top lists are semantic <ol> elements — screen readers announce "Top Korrespondenten, list 6 items, 1 of 6 Walter Dieckmann, 184 Briefe".
  • Tag cloud is a <ul> of <li><a>. The visual size does not carry meaning that text cannot — the count is not exposed to screen readers via size, only via the tooltip / aria-label "Schlagwort Verlag, {count} Briefe".
  • Focus ring: focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 on every interactive element (histogram bars, top-list rows, tag chips, header CTA).
  • Touch targets: CTA 44 px, histogram bar 32 px min width (padded invisibly if needed), top-list row 44 px on mobile / 32 px on desktop, tag chip 44 px min width.
  • prefers-reduced-motion: disable bar width animation on first render and the tag-chip hover lift. The skeleton shimmer collapses to a static gradient.

06Implementation Notes — Backend + Components

New backend endpointaggregation for dashboard
FieldValueNote
RouteGET /api/persons/{id}/dashboardSeparate from /api/persons/{id} to keep person entity lean and let the dashboard query be cache-friendly
Permission@RequirePermission(Permission.READ_ALL)Same as /api/persons/{id}
CacheServer-side cache keyed on (personId, dataVersion)Invalidate on Document write that references this person (see DocumentService update hooks)
Response schemasee next tableAll counts server-computed — never client-computed
Response schemaPersonDashboardDTO
FieldTypeNote
totalCountintoutCount + inCount
outCountintLetters where this person is sender
inCountintLetters where this person is in receivers
yearSpanintlatestYear - earliestYear + 1 (null when no letters)
correspondentCountintDistinct counterparts
activityByYearMap<int, int>year → count · always contiguous (missing years = 0 · dashboard decides display)
peakYearintYear with most letters (null when no letters)
peakYearCountint
topCorrespondentsList<CorrespondentTileDTO>Max 10 · sorted desc · each has personId · displayName · count
topLocationsList<LocationTileDTO>Max 10 · each has location · count
topTagsList<TagTileDTO>Max 20 · each has tagId · label · count · frontend buckets into size tiers
Component structurenew files + changes
FileResponsibilityChange
PersonDashboard.svelteOrchestrator · renders header + stats + sectionsnew
StatStrip.svelte4-cell stats grid with direction colouringnew
ActivityHistogram.svelteOne bar per year, peak highlight, hover tooltipnew
DistributionBar.svelteAlready introduced in briefwechsel-thumbnail-rows-spec.html · re-used here with different labelsshared
TopTileList.svelteOrdered list of tiles (name + bar + count) — used for correspondents and locationsnew · generic
TagCloud.svelteFrequency-sized chips with size bucketsnew
PersonPageShell.svelte / +page.svelteRenders 2-column gridReplace CoCorrespondentsList with PersonDashboard. Remove coCorrespondents derivation in +page.svelte — dashboard owns it.
+page.server.tsLoads person dataAdd parallel call to /api/persons/{id}/dashboard; keep error handling identical
Shipping order.
  • Phase 1 — backend endpoint GET /api/persons/{id}/dashboard with PersonDashboardDTO. Cache on person-write hooks. Tests for empty / sparse / full.
  • Phase 2PersonDashboard.svelte + its six sub-components. Replaces CoCorrespondentsList in the right column.
  • Phase 3 — new query params on /briefwechsel: direction, location, tagId. Wire every dashboard element to them.
  • Phase 4 — axe-playwright tests at 320 / 768 / 1440 in light + dark. Visual regression snapshots for all four states.