# Mission-Control-Streifen — Finale Spec

Issue #240 Leonie Voss — UX & Accessibility 15. April 2026 v3 — Final
src/routes/+page.svelte · src/lib/components/DashboardMissionControl.svelte · +page.server.ts

## Entscheidung

Der bestehende Dashboard-Aufbau (Neueste Aktivität links, DropZone + Metadaten-Widget rechts) bleibt unverändert. Unterhalb des Zwei-Spalten-Gitters erscheint ein neuer vollbreiter Mission-Control-Streifen mit drei gleichwertigen Spalten: Rahmen einzeichnen (Segmentierung, kein Vorwissen nötig), Text eintippen (Transkription, Kurrent hilfreich), Lesefertig ✓ (Belohnungsbereich).

Die „Transkription fehlt"-Spalte aus Issue #240 wird in Segmentierung + Transkription aufgeteilt, um eine klare Beitragspyramide zu schaffen: Jeder kann Rahmen einzeichnen — nicht jeder kann Kurrent lesen. Ein wöchentlich rotierender Sort mit Experten-gesucht-Escape-Hatch verhindert, dass schwer lesbare Dokumente dauerhaft die Spalte blockieren.

Dokument-Lebenszyklus
Kein Segment
0 Annotationen
→ Spalte 1
→
Segmentiert
Rahmen da, wenig Text
→ Spalte 2
→
In Review
Text da, reviewed < 90 %
→ Spalte 2
→
Lesefertig ✓
reviewed ≥ 90 %
→ Spalte 3
„Segmentiert" und „In Review" landen beide in Spalte 2 — unterschieden durch den per-Dokument-Balken (0 Blöcke vs. N Blöcke).
Spalte 1 — Rahmen einzeichnen

Dokumente ohne Annotationsrahmen. Kein Kurrent nötig — Textblöcke markieren reicht.

Bedingung: `annotation_count = 0`

Sort: Wöchentliche Rotation (seeded shuffle, s. u.)

Fortschritt: Wochenpuls „↑ +5 diese Woche", kein globaler Balken

Spalte 2 — Text eintippen

Annotationen vorhanden, aber Text fehlt oder reviewed < 90 %. Kurrent-Kenntnisse hilfreich.

Bedingung: `annotation_count > 0 AND reviewed_pct < 0.90`

Sort: Teilfortschritt zuerst, dann wöchentliche Rotation; `needsExpert`-Flagge schiebt nach hinten

Fortschritt: Per-Dokument-Balken „3 / 8 Blöcke"

Spalte 3 — Lesefertig ✓

Reviewed ≥ 90 %. Keine Aufgabe — Einladung zum Lesen.

Bedingung: `reviewed_pct >= 0.90`

Sort: Neueste zuerst

Fortschritt: „94 % geprüft" als Text — kein Balken, die mint-Spalte ist das Signal

Leerstand: Cross-Column-Redirect zu Spalte 1

Sortierstrategie — Das „zu schwer"-Problem
Schwer lesbare Dokumente blockieren die Spalte
Wenn dieselben 3 Dokumente immer oben stehen und niemand sie lesen kann, stoppt die Transkription komplett.
Problem: Bei 1 500 Dokumenten ohne Transkription und sortiert nach `updated_at` können dieselben 3 besonders schwer lesbaren Dokumente dauerhaft die Spalte blockieren. Jeder öffnet sie, gibt auf, und die Spalte wird zur Sackgasse.

#### Option 1 — Zufällig pro Seitenaufruf

`ORDER BY RANDOM()`

Jeder Besuch zeigt andere Dokumente. Kein Aufwand, aber chaotisch — kein Nutzer sieht ein Dokument zweimal, kann nicht gezielt zurückkehren.

+ Null Aufwand− Chaotisch− Kein stabiles Lesezeichen
★ Empfohlen

#### Option 2 — Teilfortschritt + wöchentliche Rotation

Dokumente mit Teilfortschritt (3/8 Blöcke) erscheinen zuerst — am ehesten abschließbar. Dokumente mit 0 Blöcken rotieren wöchentlich durch einen deterministischen Shuffle.

`ORDER BY textedBlocks DESC, HASHTEXT(id || EXTRACT(WEEK FROM NOW())::text)`
+ Konsistent innerhalb einer Woche+ Bringt leichte Dokumente an die Oberfläche+ Kein neues Datenbankfeld

#### Option 3 — Manuelle Schwierigkeitsbewertung

Beitragende bewerten Dokumente 1–3 nach Versuch. Einfache Dokumente erscheinen zuerst.

Beste Langzeitlösung — braucht aber Bewertungs-UI auf der Enrich-Seite und Signalakkumulation.

+ Selbstverbessernd− UI-Aufwand− Braucht Zeit bis Signal
Escape-Hatch: „Experten gesucht"-Flagge (Option 2 ergänzen)
Im Enrich-Bereich: ein einzelner Button „Zu schwer — Hilfe gesucht". Setzt `Document.needsExpert = true` (1 Boolean, keine Migration wenn Flyway-Migration V{n} hinzugefügt wird). In der Transkriptions-Spalte zeigen flagged Dokumente einen lila Badge und werden hinter unflagged Dokumenten einsortiert. Kein Leaderboard, keine Scham — nur ein ehrliches Signal an die Community.
Mockup: Experten-gesucht-Badge in der Transkriptions-Zeile
Reisepass Opa Heinrich 3 / 8 Blöcke
37 %
Standesamt Breslau 1872 Experten gesucht
Schrift besonders schwer lesbar — Hilfe willkommen

Element | SQL / Tailwind | Wert | Hinweis
--- | --- | --- | ---
Sort Transkription | `ORDER BY textedBlocks DESC, HASHTEXT(id::text || EXTRACT(WEEK FROM NOW())::int::text)` | — | Kein neues Feld nötig; ändert sich automatisch jede Woche
`needsExpert`-Flag | `ALTER TABLE documents ADD COLUMN needs_expert BOOLEAN NOT NULL DEFAULT FALSE` | Flyway `V{n}__add_needs_expert.sql` | Flagged Docs ans Ende: `ORDER BY needs_expert ASC, ...`
Experten-Badge | `inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-purple-50 border border-purple-200 text-purple-700` | Kontrast 6,8:1 ✓ | Nur wenn `doc.needsExpert === true`
„Zu schwer"-Button (Enrich) | `text-xs text-gray-400 hover:text-gray-600 underline underline-offset-2` | — | Unscheinbar — kein roter Knopf, keine Scham
Endpoint (Flagge setzen) | `PATCH /api/documents/{id}/needs-expert` | `@RequirePermission(READ_ALL)` | Jeder angemeldete Nutzer darf flaggen

Mockup — Desktop, normaler Zustand
⌕
Dokumente durchsuchen…
Neueste Aktivität
Brief von Oma Martha, 1943
Karl Raddatz
12. Apr
Taufurkunde Karl Raddatz
Standesamt
9. Apr
Postkarte aus Breslau
Martha Raddatz
7. Apr
Familienfoto Sommer 1952
Unbekannt
3. Apr
47 Dokumente · 12 Personen
↑
Datei hochladen
Drag & Drop
Metadaten fehlen
Familienfoto 1952
Titel fehlt
Standesamtsurkunde
Datum fehlt
Alle 5 anzeigen →
Was braucht Aufmerksamkeit?
Rahmen einzeichnen
✓ Ohne Vorkenntnisse
↑ +5 diese Woche· 1 480 offen
MR
TG
AS
+ 2
Taufurkunde Karl R.
Noch keine Rahmen
Standesamt 1889
Noch keine Rahmen
Heiratsurkunde 1921
Noch keine Rahmen
Jetzt einzeichnen →
Text eintippen
Kurrent hilfreich
↑ +2 diese Woche· 8 offen
MR
1 Person
Reisepass Opa Heinrich
3 / 8 Blöcke
Brief v. Oma Martha 1943
0 / 6 Blöcke
Standesamt Breslau 1872
Experten gesucht
Schrift besonders schwer lesbar
Jetzt tippen →
Lesefertig ✓
3 Dokumente bereit
MR
TG
Postkarte aus Breslau 1943
100 % geprüft
Brief Oma Martha 1938
95 % geprüft
Heiratsurkunde 1921
91 % geprüft
Alle 3 lesen →
Desktop (55 %) — normaler Zustand: Teilfortschritt oben, Experten-gesucht-Dokument unten in Spalte 2
Mockup — Desktop, frühe Projektphase (Lesefertig leer)
⌕
Dokumente durchsuchen…
Neueste Aktivität
Brief von Oma Martha, 1943
12. Apr
Taufurkunde Karl Raddatz
9. Apr
1 500 Dokumente · 12 Personen
↑
Datei hochladen
Drag & Drop
Metadaten fehlen
Familienfoto 1952
Standesamtsurkunde
Alle anzeigen →
Was braucht Aufmerksamkeit?
Rahmen einzeichnen
✓ Ohne Vorkenntnisse
↑ +3 diese Woche· 1 498 offen
MR
1 Person
Taufurkunde Karl R.
Standesamt 1889
Heiratsurkunde 1921
Jetzt einzeichnen →
Text eintippen
Kurrent hilfreich
↑ +1 diese Woche· 2 offen
MR
1 Person
Brief v. Oma Martha 1943
0 / 6 Blöcke
Reisepass Opa Heinrich
0 / 4 Blöcke
Jetzt tippen →
✦
Noch kein Dokument lesefertig
Erscheint hier sobald die Transkription abgeschlossen ist.
Jetzt mithelfen →
Desktop (55 %) — frühe Phase: 1 500 Dokumente ohne Transkription, Wochenpuls zeigt Schwung statt Berg

Mockup — Mobil 320 px

Die rechte Spalte (DropZone + Metadaten) erscheint auf Mobil zuerst im DOM (`lg:order-last` schiebt sie auf Desktop nach rechts). Der Streifen stapelt seine drei Spalten vertikal. Jede Spalte hat volle Breite — keine Overflow-Probleme.

⌕ Dokumente…
↑
Hochladen
Metadaten fehlen
Familienfoto 1952
Standesamtsurkunde
Neueste Aktivität
Brief von Oma Martha
Taufurkunde Karl R.
1 500 Dok. · 12 Pers.
Was braucht Aufmerksamkeit?
Rahmen einzeichnen
✓ Ohne Vorkenntnisse
↑ +5 diese Woche· 1 480 offen
Taufurkunde Karl R.
Standesamt 1889
Jetzt einzeichnen →
Text eintippen
Kurrent hilfreich
↑ +2 diese Woche· 8 offen
Reisepass Opa Heinrich
3 / 8 Blöcke
Brief v. Oma Martha 1943
0 / 6 Blöcke
Jetzt tippen →
Lesefertig ✓
3 bereit
Postkarte 1943
100 %
Brief Oma 1938
95 %
Alle lesen →
Mobil 320 px — Streifen stapelt vertikal, volle Breite je Spalte
Mobile-Reihenfolge (DOM)

1. Suchleiste
2. DropZone (write users only)
3. Metadaten fehlen
4. Neueste Aktivität
5. Was braucht Aufmerksamkeit?
   1. Rahmen einzeichnen
   2. Text eintippen
   3. Lesefertig ✓

Touch targets: Alle CTA-Buttons: `min-h-[44px]` (WCAG 2.2). Dokument-Zeilen in den Spalten: `min-h-[44px] py-2`. Der „Zu schwer"-Button auf der Enrich-Seite: `min-h-[44px]` als Icon-Button mit `aria-label`.

Engagement-Elemente — Zusammenfassung

#### ① Skill-Pill

Unter jedem Spaltentitel. „Ohne Vorkenntnisse" (grün) vs. „Kurrent hilfreich" (navy-neutral). Senkt die Hemmschwelle — Neueinsteiger sehen sofort, was ohne Kurrent-Kenntnisse möglich ist.

`bg-green-50 border-green-200 text-green-800` / `bg-surface border-line text-ink`

#### ② Wochenpuls

„↑ +5 diese Woche · 1 480 offen" statt globalem Fortschrittsbalken. Zeigt Schwung, nicht den Berg. Psychologisch: 0,8 %-Balken ist demotivierender als kein Balken.

`SELECT COUNT(*) WHERE created_at > NOW() - INTERVAL '7 days'`

#### ③ Per-Dokument-Balken

Nur in Spalte 2, nur wenn `annotation_count > 0`. Richtiger Maßstab: 8 Blöcke sind in einer Sitzung abschließbar. Zeigt auch, welche Dokumente „fast fertig" sind.

`width: {textedBlocks / totalBlocks * 100}%`; Guard: `totalBlocks === 0 → width: 0`

#### ④ Contributor-Avatare

Max. 3 Initialen-Bubbles der letzten Beitragenden pro Spalte. Kein Leaderboard (Wettbewerb) — soziale Sichtbarkeit (Zugehörigkeit). Farbe deterministisch aus User-ID-Hash.

DTO: `lastContributors: [{initials, colorIndex}]` — nur Initialen, keine Namen (Nora)

#### ⑤ „Starte hier →"-CTA

Ein einziger opinionated Button je Aufgaben-Spalte, der direkt zum nächsten Dokument springt. Entscheidungslähmung ist der Hauptgrund für Non-Participation bei Familienprojekten.

`/enrich?filter=NEEDS_SEGMENTATION&next=1` (Segmentierung)
`/enrich?filter=NEEDS_TRANSCRIPTION&next=1` (Transkription)

#### ⑥ Lesefertig-Leerstand → Redirect

Wenn Spalte 3 leer ist (frühe Phase), erscheint kein toter Endpunkt sondern: „Erscheint hier, sobald die Transkription abgeschlossen ist — jetzt mithelfen →". Der Link springt zu Spalte 1.

`{#if readyToRead.length === 0}` → `DashboardReadyToReadEmpty.svelte`

Implementation Reference

Element | Tailwind-Klassen | Pixel / Wert | Hinweis
--- | --- | --- | ---
Streifen-Wrapper | `mt-4 bg-white border border-line rounded-sm p-6` | padding 24 px | Direkt nach bestehendem `div.mt-4.grid`
Streifen-Titel | `text-xs font-bold uppercase tracking-widest text-gray-400 mb-4` | 12 px / 700 | Standard-Section-Title-Muster
3-Spalten-Grid | `grid grid-cols-1 gap-4 sm:grid-cols-3` | gap 16 px | sm = 640 px; darunter stapeln
Segmentierung-Spalte | `bg-surface rounded-sm border border-line p-4 flex flex-col gap-3` | — | Neutral
Transkription-Spalte | `bg-surface rounded-sm border border-line p-4 flex flex-col gap-3` | — | Neutral — es ist eine Aufgabe
Lesefertig-Spalte (gefüllt) | `bg-mint/10 rounded-sm border border-mint p-4 flex flex-col gap-3` | — | Mint-Ton = Erfolg
Lesefertig-Spalte (leer) | `flex flex-col items-center justify-center text-center bg-mint/5 border border-dashed border-mint rounded-sm p-6 min-h-[120px]` | min-h 120 px | Kein toter Endpunkt
Skill-Pill easy | `inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-green-50 border border-green-200 text-green-800` | Kontrast 9,7:1 ✓ AAA | —
Skill-Pill kurrent | `inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-surface border border-line text-ink` | Kontrast 14,5:1 ✓ AAA | Neutral — kein Abschreck-Signal
Wochenpuls-Zahl | `text-xs font-semibold text-green-700` (Seg.) / `text-ink` (Trans.) | 12 px | Kein globaler Balken
Per-Dokument-Track | `flex-1 h-1 bg-navy/20 rounded-full overflow-hidden` | h 4 px | Nur wenn `annotation_count > 0`
Per-Dokument-Fill | `h-full bg-ink rounded-full transition-all` + `style="width:{pct}%"` | — | Guard: `totalBlocks === 0 → 0%`
Lesefertig-Prozent | `text-xs font-semibold text-green-800` | 12 px | Kein Balken — mint-Spalte ist das Signal
Contributor-Avatar | `w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold text-white shrink-0` | 24 × 24 px | Farbe: 6 Werte, Index = `userIdHash % 6`
CTA-Button (primär) | `block w-full text-center text-xs font-semibold text-white bg-ink rounded-sm py-2 mt-2 hover:bg-ink-2 transition-colors focus-visible:ring-2 focus-visible:ring-ink focus-visible:ring-offset-1` | min-h 36 px | `aria-label` mit Dokumenttitel falls nötig
CTA-Button (ghost, Leerstand) | `inline-flex items-center text-xs font-semibold text-ink border border-ink rounded-sm px-3 py-2 hover:bg-ink hover:text-white transition-colors` | min-h 36 px | —
Experten-gesucht-Badge | `inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-purple-50 border border-purple-200 text-purple-700` | Kontrast 6,8:1 ✓ AA | Nur wenn `doc.needsExpert === true`
Sichtbarkeit Streifen | `{#if needsSegmentation.length > 0 || needsTranscription.length > 0 || readyToRead.length > 0}` | — | Streifen verschwindet wenn alle drei Buckets leer
Dokument-Zeile Mindesthöhe | `min-h-[44px] flex items-start py-2` | 44 px ✓ WCAG 2.2 | Gilt für alle klickbaren Zeilen

Backend — neue Endpoints & Queries

Endpoint / Query | Bedingung | Sort | Auth
--- | --- | --- | ---
`GET /api/documents/needs-segmentation?size=3` | `NOT EXISTS (SELECT 1 FROM document_annotations WHERE document_id = d.id)` | `HASHTEXT(id::text || week::text)` | `READ_ALL`
`GET /api/documents/needs-transcription?size=3` | `EXISTS annotation AND (no blocks OR reviewed_pct < 0.90)` | `textedBlocks DESC, needs_expert ASC, HASHTEXT(...)` | `READ_ALL`
`GET /api/documents/ready-to-read?size=3` | `reviewed_pct >= 0.90` | `updated_at DESC` | `READ_ALL`
`PATCH /api/documents/{id}/needs-expert` | Setzt `needs_expert = true` | — | `READ_ALL` (jeder Nutzer darf flaggen)
`GET /api/stats/strip-activity` | Wochenpuls: `COUNT(*) WHERE created_at > NOW() - INTERVAL '7 days'` pro Bucket | — | `READ_ALL`
Flyway-Migration | `ALTER TABLE documents ADD COLUMN needs_expert BOOLEAN NOT NULL DEFAULT FALSE` | — | V{n}__add_needs_expert_flag.sql
Index prüfen (Tobias) | `document_annotations(document_id)`, `transcription_blocks(document_id, reviewed)` | — | EXPLAIN ANALYZE vor Merge
Division durch 0 (Sara) | Alle reviewed_pct-Queries: `CASE WHEN COUNT(*) = 0 THEN 0 ELSE SUM(...)::float / COUNT(*) END` | — | —

Neue Svelte-Komponenten

#### `DashboardMissionControl.svelte`

Wrapper für den vollbreiten Streifen. Props: `needsSegmentation`, `needsTranscription`, `readyToRead`, `weeklyActivity`. Rendert die drei Spalten und ist komplett unsichtbar wenn alle Arrays leer sind.

#### `DashboardSegmentationCol.svelte`

Spalte 1: Skill-Pill, Wochenpuls, Avatare, Dokumentliste, CTA. Keine Balken — keine Dokument-Metadaten vorhanden.

#### `DashboardTranscriptionCol.svelte`

Spalte 2: Skill-Pill, Wochenpuls, Avatare, per-Dokument-Balken, Experten-Badge bei `needsExpert`, CTA.

#### `DashboardReadyToReadCol.svelte`

Spalte 3: Zeigt gefüllten Zustand (Liste mit %-Text) oder leeren Zustand (Cross-Column-Redirect zu Segmentierung).

Bestehende Komponente bleibt: `DashboardNeedsMetadata.svelte` ist unverändert — sie lebt weiterhin in der rechten Spalte. Der Mission-Control-Streifen ist vollständig additiv und ändert nichts am bestehenden Layout.