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 @@ + + +
+ + ++ 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. +
+ +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.
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.
+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.
+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.
frontend/src/routes/documents/+page.svelte and +page.server.ts. AppNav "Documents" tab href changes from / to /documents. Homepage +page.svelte loses dual-mode — always renders dashboard. No redirect from /?q=….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.
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.
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.
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.
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.
+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.
+Flex-1, min-width 0. Padding p-4 pr-5. Right border border-r border-line-2.
Title — font-serif text-base font-bold text-ink with search highlight underlines. mb-1.5.
Snippet — font-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.
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.
+<a> element, always ≥44px tall given the content. Row hover: hover:bg-muted/50 transition-colors duration-200.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.
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.
+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".
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.
+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.| Field | Type | Source | Notes |
|---|---|---|---|
completionPercentage | int (0–100) | COUNT(reviewed annotation blocks) / COUNT(all blocks) | 0 when no blocks exist |
contributors | ActivityActorDTO[] | Distinct users with annotation_block contributions, ordered by recency | Max 4; reuse existing DTO |
archiveBox | String? | Already on Document entity — just not in search response | Expose existing field |
archiveFolder | String? | Already on Document entity — just not in search response | Expose existing field |
| Element | Tailwind classes | Pixels / value | Notes |
|---|---|---|---|
| Page chrome | |||
| Search bar wrapper | bg-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-20 | padding 14px responsive | Topbar = 1px accent + 64px nav = 65px. Negative margins break out of container padding so bar spans full container width. |
| Search input | flex-1 h-9 border border-ink rounded-sm px-3 font-sans text-sm text-ink bg-white | height 36px | Active: navy border |
| Sort bar wrapper | bg-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-20 | padding 10px responsive | Stacks 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.5 | height 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.5 | height 28px | Navy fill when active |
| Filter panel wrapper | bg-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-4 | padding 16px responsive | Use Svelte slide transition; stacks vertically on mobile |
| List body | py-5 | vertical padding only | No extra horizontal padding — app container handles it |
| Year group card | |||
| Card container | border border-line bg-surface shadow-sm mb-4 overflow-hidden | — | Matches current DocumentList outer div exactly |
| Year header | bg-sand border-b border-line px-5 py-1.5 font-sans text-[10px] font-bold uppercase tracking-widest text-ink-3 | padding 6px 20px | — |
| Row list | divide-y divide-line-2 | — | Matches current <ul> pattern |
| Document row | |||
Row wrapper <li> | group transition-colors duration-200 hover:bg-muted/50 | — | Same hover pattern as current |
| Row inner (link) | block sm:flex sm:items-stretch | — | Full-row <a href="/documents/{id}">; flex only on sm+ |
| Left column | p-4 sm:flex-1 sm:min-w-0 sm:pr-5 sm:border-r sm:border-line-2 | padding 16px | Right 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-2 | sm: 192px · lg: 240px | Hidden on mobile; narrower on tablet |
| Mobile metadata grid | sm:hidden border-t border-line-2 mt-3 pt-3 grid grid-cols-2 gap-x-4 gap-y-0.5 | — | 2×2 compact grid shown only on mobile, inside left col |
| Mobile meta bottom row | sm:hidden flex items-center justify-between mt-3 | — | Ring + contributors on mobile, shown only <sm |
| Document title | font-serif text-base font-bold text-ink mb-1.5 leading-snug group-hover:underline | 16px / 700 | — |
| Snippet text | font-serif text-sm italic text-ink-2 line-clamp-2 mb-2 | 14px | Only when snippet present |
| Meta label | font-sans text-[10px] font-bold uppercase tracking-wide text-ink-3 mr-1.5 | 10px / 700 | DATE · FROM · TO · ARCHIVE |
| Meta value | font-sans text-[11px] text-ink-2 | 11px | — |
| Progress ring | |||
| SVG container | relative w-9 h-9 flex-shrink-0 | 36×36px | — |
| Track circle | stroke="var(--c-sand)" stroke-width="3" | r=13, circumference 81.7px | — |
| Fill arc | stroke="var(--c-accent)" stroke-width="3" stroke-linecap="round" | dasharray = pct/100 × 81.7 | rotate(−90deg) |
| Percentage label | absolute inset-0 flex items-center justify-center font-sans text-[8px] font-bold | 8px / 800 | Mint when >0, gray-400 when 0 |
| New files | |||
NEW frontend/src/routes/documents/+page.svelte | — | — | Document list page (extract from homepage) |
NEW frontend/src/routes/documents/+page.server.ts | — | — | Loads search results, same API call as current homepage |
CHANGED frontend/src/routes/AppNav.svelte | — | — | Documents tab href: / → /documents |
CHANGED frontend/src/routes/+page.svelte | — | — | Remove dual-mode logic; always render dashboard |
CHANGED frontend/src/routes/+page.server.ts | — | — | Remove search branch; always fetch dashboard data |
CHANGED frontend/src/routes/DocumentList.svelte | — | — | Refactor to new two-column layout + year cards |
NEW query backend/.../DocumentSearchRepository | — | — | Add completionPercentage + contributors to search projection |