docs(c4): accuracy audit — split L3 diagrams, add 6 new sub-diagrams, fix all stale content #448

Merged
marcel merged 27 commits from docs/post-refactor-accuracy-audit into main 2026-05-06 20:00:16 +02:00
Showing only changes of commit 18a7ee1fa5 - Show all commits

View File

@@ -1,165 +0,0 @@
# Reader Dashboard — Design Spec
**Date:** 2026-05-06
**Status:** Approved for implementation planning
---
## Problem
The archive has two distinct user groups:
- **Contributors** — transcribe, annotate, upload. Comfortable with the current dashboard (MissionControlStrip, EnrichmentBlock, enrichment queue, activity feed).
- **Readers** — browse and consume finished content. Older, less technical. Currently overwhelmed by contribution-focused UI they cannot use and do not need.
---
## Solution
Introduce a **permission-gated reader dashboard** that replaces the current dashboard for users without `WRITE_ALL` or `ANNOTATE_ALL` (including pure readers and story writers). The contributor dashboard remains unchanged.
---
## Detection Logic
```
isReader = !canWrite && !canAnnotate
showDrafts = canBlogWrite // overlays on reader dashboard only
```
`BLOG_WRITE` users land on the **reader dashboard** (not the contributor dashboard), because story writers are conceptually closer to readers than to transcribers. The drafts module appears on top of the reader layout when `canBlogWrite` is true.
`canWrite`, `canAnnotate`, `canBlogWrite` are already derived in `+layout.server.ts` and available in `$page.data` on all routes. No new backend work required for detection.
---
## Reader Dashboard Layout
Five zones, rendered top-to-bottom:
### 1. Greeting
Unchanged from current dashboard — personalized, time-based greeting. Warm entry point for less technical users.
### 2. Stats Strip
Three linked stat tiles in a single row:
| Tile | Value | Link |
|---|---|---|
| Dokumente | Total document count | `/documents` |
| Personen | Total person count | `/persons` |
| Geschichten | Total published story count | `/geschichten` |
Each tile is a full-area anchor (`<a>`). Values come from the existing stats endpoint already called by the current dashboard.
### 3. Drafts Module *(conditional — BLOG_WRITE only)*
Shown only when `canBlogWrite` is true, between the stats strip and the person chips.
- Section heading: "Meine Entwürfe"
- Lists all draft stories authored by the current user, sorted by `updated_at DESC`
- Each entry shows: story title + "Entwurf · zuletzt bearbeitet vor X" relative timestamp
- Each entry links to `/geschichten/[id]/edit`
- Empty state: "Keine Entwürfe" (no empty-state CTA needed — they can create from `/geschichten`)
This module appears on the reader dashboard because a user can hold `READ_ALL + BLOG_WRITE` without `WRITE_ALL`.
### 4. Person Chips
Top **4** persons by total document count, each linking to `/persons/[id]`. Followed by an overflow link "Alle N Personen →" pointing to `/persons`.
- Chip content: display name + document count (e.g., "Käthe Raddatz · 23 Dok.")
- Layout: `flex flex-wrap gap-2` — chips reflow gracefully at narrower viewports
- Data source: persons list endpoint, sorted by document count DESC, limit 4
Rationale: readers most often browse by person rather than searching for a specific document. Surfacing the most-documented family members at the top answers "where do I start?"
### 5. Two-Column Content Row
Side-by-side at desktop; stacks to single column on mobile (below `md` breakpoint).
**Left column — "Zuletzt aktualisiert" (flex: 3)**
5 most recently updated documents, sorted by `updated_at DESC`. No status filter — uploads and transcription updates are both relevant.
Each row shows:
- Document thumbnail placeholder (or actual thumbnail if available)
- Document title (linked to `/documents/[id]`)
- Sender name (linked to `/persons/[id]`) if present; omitted if document has no sender + relative timestamp
**Right column — "Geschichten" (flex: 2)**
3 most recently published stories.
Each entry shows:
- Story title (italic serif, linked to `/geschichten/[id]`)
- First ~150 characters of body text as excerpt
- Relative publication timestamp
---
## What Is Hidden from Readers
| Component | Reason |
|---|---|
| `MissionControlStrip` | Transcription queue — contributor-only |
| `EnrichmentBlock` | Incomplete-document workflow — contributor-only |
| `DashboardResumeStrip` | Contribution resume metric — meaningless to readers |
| `DashboardFamilyPulse` | Contribution-focused activity metrics |
| `DashboardActivityFeed` | Replaced by the simpler "Zuletzt aktualisiert" feed |
| `DropZone` | Already gated on `canWrite` — unchanged |
---
## Backend Changes
The reader dashboard reuses data from endpoints already called by the existing dashboard where possible. New or adapted calls:
| Data | Endpoint | Notes |
|---|---|---|
| Stats (docs, persons, stories) | Existing stats endpoint | Already fetched |
| Top 4 persons by doc count | `GET /api/persons?sort=documentCount,desc&size=4` | Verify sort param exists; add if not |
| Recent 5 documents | `GET /api/documents?sort=updatedAt,desc&size=5` | Verify sort param exists; add if not |
| Recent 3 stories | `GET /api/geschichten?published=true&sort=updatedAt,desc&size=3` | Verify sort param and published filter |
| Draft stories (BLOG_WRITE only) | `GET /api/geschichten?published=false&authorId=currentUser&size=10` | Verify author filter exists; add if not |
The `+page.server.ts` load function should branch on `isReader`: fetch the reader data set instead of the contributor data set. This avoids loading transcription queues, enrichment data, and weekly stats for users who will never see them.
---
## Frontend Changes
- `+page.server.ts`: add `isReader` flag derived from layout data; branch fetch logic
- `+page.svelte`: conditional render — reader layout vs. current contributor layout
- New components (all in `src/lib/shared/dashboard/`):
- `ReaderStatsStrip.svelte` — the three linked stat tiles
- `ReaderPersonChips.svelte` — top-N person chips + overflow link
- `ReaderRecentDocs.svelte` — recent documents feed
- `ReaderRecentStories.svelte` — recent stories feed
- `ReaderDraftsModule.svelte` — draft stories (rendered conditionally on `canBlogWrite`)
---
## Non-Functional Requirements
- **NFR-PERF-001**: Reader dashboard must load in ≤ 2 s on broadband (time-to-interactive). Achieved by fetching only the 4 lean endpoints above instead of the current 10.
- **NFR-A11Y-001**: All stat tiles and person chips must be keyboard-navigable (`<a>` elements, not `<div onclick>`).
- **NFR-RESP-001**: Two-column row stacks to single column at `< md` (768 px). Person chips wrap via `flex-wrap`.
- **NFR-I18N-001**: All new section headings and labels must have keys in `messages/{de,en,es}.json`.
---
## Out of Scope
- Mobile-specific reader dashboard (responsive reflow is sufficient for now)
- Admin dashboard variant
- Any change to the contributor dashboard
- Personalization / "favourite persons" feature (possible future enhancement)
- Notification or messaging features for readers
---
## Open Questions
| ID | Question | Blocks |
|---|---|---|
| OQ-01 | Does `GET /api/persons` support `sort=documentCount,desc`? | ReaderPersonChips data |
| OQ-02 | Does `GET /api/documents` support `sort=updatedAt,desc`? | ReaderRecentDocs data |
| OQ-03 | Does the stories endpoint support `published=false` + author filter for drafts? | ReaderDraftsModule data |
| OQ-04 | Should the "Zuletzt aktualisiert" label distinguish uploads from transcription updates (e.g., badge)? | ReaderRecentDocs UX |