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
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
ElementSQL / TailwindWertHinweis
Sort TranskriptionORDER BY textedBlocks DESC, HASHTEXT(id::text || EXTRACT(WEEK FROM NOW())::int::text)Kein neues Feld nötig; ändert sich automatisch jede Woche
needsExpert-FlagALTER TABLE documents ADD COLUMN needs_expert BOOLEAN NOT NULL DEFAULT FALSEFlyway V{n}__add_needs_expert.sqlFlagged Docs ans Ende: ORDER BY needs_expert ASC, ...
Experten-Badgeinline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-purple-50 border border-purple-200 text-purple-700Kontrast 6,8:1 ✓Nur wenn doc.needsExpert === true
„Zu schwer"-Button (Enrich)text-xs text-gray-400 hover:text-gray-600 underline underline-offset-2Unscheinbar — 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
MR
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)
MR
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.

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 ✓

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
ElementTailwind-KlassenPixel / WertHinweis
Streifen-Wrappermt-4 bg-white border border-line rounded-sm p-6padding 24 pxDirekt nach bestehendem div.mt-4.grid
Streifen-Titeltext-xs font-bold uppercase tracking-widest text-gray-400 mb-412 px / 700Standard-Section-Title-Muster
3-Spalten-Gridgrid grid-cols-1 gap-4 sm:grid-cols-3gap 16 pxsm = 640 px; darunter stapeln
Segmentierung-Spaltebg-surface rounded-sm border border-line p-4 flex flex-col gap-3Neutral
Transkription-Spaltebg-surface rounded-sm border border-line p-4 flex flex-col gap-3Neutral — es ist eine Aufgabe
Lesefertig-Spalte (gefüllt)bg-mint/10 rounded-sm border border-mint p-4 flex flex-col gap-3Mint-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 pxKein toter Endpunkt
Skill-Pill easyinline-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-800Kontrast 9,7:1 ✓ AAA
Skill-Pill kurrentinline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-surface border border-line text-inkKontrast 14,5:1 ✓ AAANeutral — kein Abschreck-Signal
Wochenpuls-Zahltext-xs font-semibold text-green-700 (Seg.) / text-ink (Trans.)12 pxKein globaler Balken
Per-Dokument-Trackflex-1 h-1 bg-navy/20 rounded-full overflow-hiddenh 4 pxNur wenn annotation_count > 0
Per-Dokument-Fillh-full bg-ink rounded-full transition-all + style="width:{pct}%"Guard: totalBlocks === 0 → 0%
Lesefertig-Prozenttext-xs font-semibold text-green-80012 pxKein Balken — mint-Spalte ist das Signal
Contributor-Avatarw-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold text-white shrink-024 × 24 pxFarbe: 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-1min-h 36 pxaria-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-colorsmin-h 36 px
Experten-gesucht-Badgeinline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-purple-50 border border-purple-200 text-purple-700Kontrast 6,8:1 ✓ AANur 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öhemin-h-[44px] flex items-start py-244 px ✓ WCAG 2.2Gilt für alle klickbaren Zeilen

Backend — neue Endpoints & Queries
Endpoint / QueryBedingungSortAuth
GET /api/documents/needs-segmentation?size=3NOT EXISTS (SELECT 1 FROM document_annotations WHERE document_id = d.id)HASHTEXT(id::text || week::text)READ_ALL
GET /api/documents/needs-transcription?size=3EXISTS annotation AND (no blocks OR reviewed_pct < 0.90)textedBlocks DESC, needs_expert ASC, HASHTEXT(...)READ_ALL
GET /api/documents/ready-to-read?size=3reviewed_pct >= 0.90updated_at DESCREAD_ALL
PATCH /api/documents/{id}/needs-expertSetzt needs_expert = trueREAD_ALL (jeder Nutzer darf flaggen)
GET /api/stats/strip-activityWochenpuls: COUNT(*) WHERE created_at > NOW() - INTERVAL '7 days' pro BucketREAD_ALL
Flyway-MigrationALTER TABLE documents ADD COLUMN needs_expert BOOLEAN NOT NULL DEFAULT FALSEV{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.