From 6494b13147256328aa32297da89128a701f01f1d Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 19 Apr 2026 21:44:45 +0200 Subject: [PATCH] docs(spec): add /documents page design spec with mobile breakpoints Co-Authored-By: Claude Sonnet 4.6 --- docs/specs/documents-page-spec.html | 665 ++++++++++++++++++++++++++++ 1 file changed, 665 insertions(+) create mode 100644 docs/specs/documents-page-spec.html diff --git a/docs/specs/documents-page-spec.html b/docs/specs/documents-page-spec.html new file mode 100644 index 00000000..f69e2ef1 --- /dev/null +++ b/docs/specs/documents-page-spec.html @@ -0,0 +1,665 @@ + + + + + +Dokumente-Seite — Design Spec + + + +
+ + +
+
+ Neue Route + Frontend + Backend +
+

Dokumente-Seite — /documents

+

+ Dedicated search and browse page for all documents. Separates the document list from the dashboard hub. Uses per-year group cards with flat divide-y rows, a horizontal split row (content left · metadata right), a circular progress ring, and contributor avatars. +

+
Spec · Leonie Voss · 2026-04-19 · Issue TBD
+
+ +
+

Design decisions

+

The hub (/) becomes pure dashboard — no more dual-mode switching. The "Documents" nav tab points to /documents, a focused search/browse page.

+

Row layout: two-column split — title and snippet occupy the full left column for maximum scan width; date, sender, receiver, archive location, progress ring and contributor avatars live in a fixed 240px right panel. This keeps metadata consistently positioned across all rows.

+

List structure: one white card container per year group (matching the current border border-line bg-surface shadow-sm pattern), rows separated by divide-y dividers — no gaps, no individual row cards. The year label is an inset header row within each card.

+

Progress ring shows work completion as a percentage (0–100%). It is driven by a new completionPercentage field on the search result DTO, computed server-side from annotation block counts. Contributor avatars require a new contributors array (initials + color) on the search DTO.

+
+ +
+ + +
+
Section 1
+
Full page mockup — filter panel open, search active
+
Scaled at ~56%. Desktop 1200px concept width.
+ +
+ +
+ + +
Hochlader
MR
+
+ + + +
+ 31 documents + Sort +
Date ↓
+
+ + Filters 1 +
+
+ +
+
+ Date range +
From
To
+
+
+ Sender +
Search person…
+
+
+ Receiver +
Search person…
+
+
+ Tags + BriefFotoPostkarteUrkunde +
+
+ +
+ + +
+
1924
+
+
+
Demo: Ierlicher Brief — Belgern
+
… Hiermit übersende ich Ihnen den gewünschten Brief meines Vaters, welcher einige interessante Hinweise zur Familiengeschichte enthält …
+
Brief
Familie
+
+
+
+
Date 31. Mai 1924
+
From Louise Aon Boden
+
To Marcel Raddatz
+
Archive Box 3 · Folder A
+
+
+
+ +
100%
+
+
MR
LS
+
+
+
+
+ + +
+
1923
+
+
+
W-0614 – 8. September 1923 – Tölz
+
… Clara schreibt über die Ankunft in Tölz und erwähnt den letzten Brief von Fauld Rupley, der noch keine Antwort erhalten hat …
+
Brief
+
+
+
+
Date 8. Sept. 1923
+
From Clara Lam
+
To Fauld Rupley
+
Archive Box 1 · Folder C
+
+
+
+ +
75%
+
+
AK
+
+
+
+
+
+
W-0196 – 2. September 1923 – B. Lichterfelde
+
… Prediger's Haushaltung enthält einen Brief; Zusammen mit der Vollmacht aus dem Vorjahr ergibt sich folgendes Bild …
+
Brief
+
+
+
+
Date 2. Sept. 1923
+
From Müller de Gruym
+
To Herbert Cram
+
+
+
+ +
40%
+
+
MR
LS
AK
+
+
+
+
+
+
W-0397 – 2. September 1923 – B. Lichterfelde
+
… zum einleitend Kommentar hieraus, den Herrn, zum Brief az sechzig und weitere Passagen …
+
Brief
+
+
+
+
Date 2. Sept. 1923
+
From Müller de Gruym
+
+
+
+ +
0%
+
+ No contributors +
+
+
+
+ +
+
+ Fig 1 — /documents · 1200px · search: "brief" · filter panel open · sort: Date ↓ +
+ +
+ + +
+
Section 2
+
Page structure & zones
+ +
+
+

① Global search bar

+

Full-width row below the topbar. Contains the search input (flex-1), result count (right of input), and "+ New Document" button. Background white, bottom border border-line. Sticky — stays visible on scroll.

+

Same search bar pattern as the current homepage. Debounce 500 ms on text input; immediate on clear.

+
+
+

② Sort / count bar

+

Slim bar below search. Shows result count (left), sort dropdown (right), and Filters toggle button (far right). Background white, bottom border border-line. Sticky — stacks below search bar on scroll.

+

Filters button shows a mint badge with active filter count. When filters are open the button fills navy.

+
+
+

③ Collapsible filter panel

+

Drops open below the sort bar. Contains four groups: Date range (two inputs), Sender (PersonTypeahead), Receiver (PersonTypeahead), Tags (clickable pills). White background, bottom border border-line.

+

Closed by default on page load unless URL already has active filter params. Animate open/close with transition-all duration-200.

+
+
+ + +
+ +
+ + +
+
Section 2b
+
Mobile breakpoints
+
Three responsive tiers: <sm (mobile), sm–lg (tablet), lg+ (desktop).
+ +
+
+

< sm — < 640px (mobile)

+

Document row: single-column block. Left/right split collapses. Metadata (date, from, to, archive) moves below the tags row as a 2×2 compact grid. Progress ring and contributor stack appear in a bottom row directly below the grid.

+

Filter panel: single-column stack (flex-col). Sort bar wraps if needed.

+
+
+

sm – lg — 640–1023px

+

Document row: two-column split restored. Metadata column narrower: sm:w-48 (192px) instead of w-60 to fit tablet viewports.

+

Sticky bars span full width via negative margins. Filter panel: flex-row flex-wrap, groups can wrap.

+
+
+

lg+ — ≥ 1024px (desktop)

+

Full two-column split. Metadata column: lg:w-60 (240px). Filter panel: four groups in a single row. Max content width max-w-7xl (1280px) — from app layout container, no extra padding on list body.

+
+
+ + +
+
+
+
+ +
+ Dokumente +
MR
+
+
+ +
+ 31 documents + Sort +
Date ↓
+
Filters
+
+
+
+
1924
+ +
+
Demo: Ierlicher Brief — Belgern
+
… Hiermit übersende ich Ihnen den gewünschten Brief
+
Brief
+
+
Date 31. Mai 1924
+
From L. von Boden
+
Archive Box 3 · A
+
To M. Raddatz
+
+
+
+ +
100%
+
+
MR
LS
+
+
+ +
+
W-0614 – Sept. 1923 – Tölz
+
… Clara schreibt über den letzten Brief von Fauld Rupley …
+
Brief
+
+
Date 8. Sept. 1923
+
From Clara Lam
+
+
To F. Rupley
+
+
+
+ +
75%
+
+
AK
+
+
+
+
+
+ Fig 2 — /documents · 375px mobile · search "brief" · filter closed +
+ +
+

Mobile row — CSS-only approach

+

No JS needed. The <a> link is always block. On sm+ the inner element switches to flex items-stretch, showing the right metadata column (hidden sm:flex) and hiding the mobile compact grid (sm:hidden).

+

This means the DOM contains both layouts simultaneously — the metadata grid inside the left column (mobile only) and the right metadata panel (sm+ only). Both share the same data, just rendered differently.

+

Minimum touch target: the entire row is the <a>, guaranteed ≥44px on mobile given title + snippet + tags + metadata grid.

+
+
+
+ +
+ + +
+
Section 3
+
Year group card
+
One card per year group. Rows inside use divide-y — no gaps between rows.
+ +
+
+

Card container

+

Matches current DocumentList outer container exactly: border border-line bg-surface shadow-sm. No border-radius (keeps it flush). Margin between consecutive year cards: mb-4.

+

Rendered only when sort = DATE. For other sort modes (SENDER, RECEIVER, TITLE) the year header is replaced by the relevant group label using the same card pattern.

+
+
+

Year header row

+

First child of each card. Background bg-sand, text text-xs font-bold uppercase tracking-widest text-ink-3. Height py-1.5 px-5. Bottom border border-b border-line.

+

Not a standalone divider — it is part of the card so the top border of the card frames the year label on three sides.

+
+
+
+ +
+ + +
+
Section 4
+
Document row — two-column split
+ +
+
+

Left column — content

+

Flex-1, min-width 0. Padding p-4 pr-5. Right border border-r border-line-2.

+

Titlefont-serif text-base font-bold text-ink with search highlight underlines. mb-1.5.

+

Snippetfont-serif text-sm italic text-ink-2 line-clamp-2 mb-2 with highlight underlines. Only rendered when a match snippet is present.

+

Tags — existing tag pill pattern bg-muted text-ink text-[10px] font-bold uppercase tracking-widest rounded px-2 py-0.5. Gap gap-1.5 flex-wrap.

+
+
+

Right column — metadata panel

+

Fixed width w-60 (240px). Padding p-3.5. Flex column, justify-between.

+

Meta lines (top group) — font-sans text-[11px] text-ink-2 mb-1. Label: font-bold uppercase tracking-wide text-[10px] text-ink-3 mr-1.5. Lines: Date · From · To · Archive (Box · Folder). Archive only rendered when archiveBox is set.

+

Bottom row — flexbox, space-between. Left: progress ring. Right: ContributorStack.

+
+
+ +
+
Accessibility: The ring conveys progress by both percentage text and arc fill — not colour alone. Contributors show initials as text inside the avatar. Both pass the redundant-cue requirement from the Leonie Voss persona. Minimum touch target for the row link: the full row is the <a> element, always ≥44px tall given the content. Row hover: hover:bg-muted/50 transition-colors duration-200.
+
+
+ +
+ + +
+
Section 5
+
Progress ring
+ +
+
+

Anatomy

+

SVG donut ring, 36×36px. Track circle: stroke="#E4E2D7" (stroke-brand-sand) width 3px. Fill arc: stroke="#A6DAD8" (stroke-accent) width 3px, stroke-linecap="round". Rotated −90° so arc starts at 12 o'clock.

+

Centre label: percentage text font-sans text-[8px] font-bold. Colour: mint (text-accent-dark) when >0%, gray-400 when 0%.

+

Circumference of r=13: 2π×13 ≈ 81.7px. Stroke-dasharray: {pct * 81.7} 81.7.

+
+
+

Data source — new API field

+

New field completionPercentage: number (0–100, integer) on the document search result DTO. Computed server-side:

+

round((reviewedBlocks / max(totalBlocks, 1)) * 100)

+

If a document has no annotation blocks yet (no transcription started), returns 0. Backend change: new subquery in the document search repository to COUNT annotation blocks (all vs. reviewed) per document, joined into the search projection.

+
+
+
+ +
+ + +
+
Section 6
+
Contributor avatar stack
+ +
+
+

Anatomy

+

Reuse existing ContributorStack.svelte component (added in commit 031f6ea). Avatars 22×22px, -ml-1.5 overlap, white 2px border.

+

Show max 3 avatars. If more: +N text element in gray-400. When no contributors: render text-[9px] text-ink-3 uppercase tracking-wide label "No contributors".

+
+
+

Data source — new API field

+

New field contributors: ActivityActorDTO[] on the document search result DTO. ActivityActorDTO already exists (used in dashboard queue items): { initials: string, color: string, name?: string }.

+

Backend: join from document → annotation_blocks → created_by → users. Distinct by user. Order by most-recent contribution. Limit 4. New query in document search repository.

+
+
+
+ +
+ + +
+
Section 7
+
Backend changes required
+ +
+
New fields on document search DTO — Two new fields must be added to the object returned by GET /api/documents/search. These require a new projection or join in the repository layer. No schema migration needed — purely computed from existing annotation_block data.
+
+ +
+ + + + + + + + +
FieldTypeSourceNotes
completionPercentageint (0–100)COUNT(reviewed annotation blocks) / COUNT(all blocks)0 when no blocks exist
contributorsActivityActorDTO[]Distinct users with annotation_block contributions, ordered by recencyMax 4; reuse existing DTO
archiveBoxString?Already on Document entity — just not in search responseExpose existing field
archiveFolderString?Already on Document entity — just not in search responseExpose existing field
+
+
+ +
+ + +
+
Section 8 — Implementation Reference
+
Exact Tailwind classes & pixel values
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ElementTailwind classesPixels / valueNotes
Page chrome
Search bar wrapperbg-white border-b border-line -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 py-3.5 flex items-center gap-3 sticky top-[65px] z-20padding 14px responsiveTopbar = 1px accent + 64px nav = 65px. Negative margins break out of container padding so bar spans full container width.
Search inputflex-1 h-9 border border-ink rounded-sm px-3 font-sans text-sm text-ink bg-whiteheight 36pxActive: navy border
Sort bar wrapperbg-white border-b border-line -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 py-2.5 flex items-center gap-3 sticky top-[113px] z-20padding 10px responsiveStacks below search bar (65 + 48 = 113px)
Filters toggle (closed)h-7 px-3 border border-line rounded-sm font-sans text-[10px] font-bold uppercase tracking-wide text-ink flex items-center gap-1.5height 28px
Filters toggle (open)h-7 px-3 bg-ink text-white rounded-sm font-sans text-[10px] font-bold uppercase tracking-wide flex items-center gap-1.5height 28pxNavy fill when active
Filter panel wrapperbg-white border-b border-line -mx-4 sm:-mx-6 lg:-mx-8 px-4 sm:px-6 lg:px-8 py-4 flex flex-col sm:flex-row sm:flex-wrap gap-4padding 16px responsiveUse Svelte slide transition; stacks vertically on mobile
List bodypy-5vertical padding onlyNo extra horizontal padding — app container handles it
Year group card
Card containerborder border-line bg-surface shadow-sm mb-4 overflow-hiddenMatches current DocumentList outer div exactly
Year headerbg-sand border-b border-line px-5 py-1.5 font-sans text-[10px] font-bold uppercase tracking-widest text-ink-3padding 6px 20px
Row listdivide-y divide-line-2Matches current <ul> pattern
Document row
Row wrapper <li>group transition-colors duration-200 hover:bg-muted/50Same hover pattern as current
Row inner (link)block sm:flex sm:items-stretchFull-row <a href="/documents/{id}">; flex only on sm+
Left columnp-4 sm:flex-1 sm:min-w-0 sm:pr-5 sm:border-r sm:border-line-2padding 16pxRight border only on sm+
Right column (sm+)hidden sm:flex sm:w-48 lg:w-60 flex-shrink-0 p-3.5 flex-col justify-between gap-2sm: 192px · lg: 240pxHidden on mobile; narrower on tablet
Mobile metadata gridsm:hidden border-t border-line-2 mt-3 pt-3 grid grid-cols-2 gap-x-4 gap-y-0.52×2 compact grid shown only on mobile, inside left col
Mobile meta bottom rowsm:hidden flex items-center justify-between mt-3Ring + contributors on mobile, shown only <sm
Document titlefont-serif text-base font-bold text-ink mb-1.5 leading-snug group-hover:underline16px / 700
Snippet textfont-serif text-sm italic text-ink-2 line-clamp-2 mb-214pxOnly when snippet present
Meta labelfont-sans text-[10px] font-bold uppercase tracking-wide text-ink-3 mr-1.510px / 700DATE · FROM · TO · ARCHIVE
Meta valuefont-sans text-[11px] text-ink-211px
Progress ring
SVG containerrelative w-9 h-9 flex-shrink-036×36px
Track circlestroke="var(--c-sand)" stroke-width="3"r=13, circumference 81.7px
Fill arcstroke="var(--c-accent)" stroke-width="3" stroke-linecap="round"dasharray = pct/100 × 81.7rotate(−90deg)
Percentage labelabsolute inset-0 flex items-center justify-center font-sans text-[8px] font-bold8px / 800Mint when >0, gray-400 when 0
New files
NEW frontend/src/routes/documents/+page.svelteDocument list page (extract from homepage)
NEW frontend/src/routes/documents/+page.server.tsLoads search results, same API call as current homepage
CHANGED frontend/src/routes/AppNav.svelteDocuments tab href: //documents
CHANGED frontend/src/routes/+page.svelteRemove dual-mode logic; always render dashboard
CHANGED frontend/src/routes/+page.server.tsRemove search branch; always fetch dashboard data
CHANGED frontend/src/routes/DocumentList.svelteRefactor to new two-column layout + year cards
NEW query backend/.../DocumentSearchRepositoryAdd completionPercentage + contributors to search projection
+
+
+ +
+ +