Files
familienarchiv/docs/specs/mission-control-strip-final.html
Marcel 5bd7f0d486
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m25s
CI / Backend Unit Tests (push) Failing after 2m38s
CI / Unit & Component Tests (pull_request) Failing after 2m11s
CI / Backend Unit Tests (pull_request) Failing after 8h41m14s
docs(#240): add Mission Control Strip spec and pattern alternatives
Adds the design decision record for how to expand the dashboard without
pushing content below the fold: a full-width 3-column strip (Segmentierung /
Transkription / Lesefertig) below the existing grid.

- dashboard-expansion-patterns.html — four pattern alternatives evaluated
  (Tabs, Accordion, Mission Control, Priority Queue) with annotated mockups,
  engagement feature proposal, and final recommendation.
- mission-control-strip-final.html — clean implementation blueprint with
  pipeline diagram, column definitions, seeded-weekly-shuffle sorting,
  expert-flag escape hatch, all Tailwind impl-ref values, and backend
  contracts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 22:48:27 +02:00

815 lines
52 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Mission-Control-Streifen — Finale Spec (Issue #240)</title>
<style>
:root{
--navy:#002850;--mint:#A6DAD8;--sand:#E4E2D7;
--surface:#FAFAF7;--bg:#E8E7E2;--border:#D8D7D0;
--text:#1C1C18;--muted:#6B6A63;--subtle:#9B9A93;
--orange:#C26A00;--orange-bg:#FEF4E2;
--green:#2E6E39;--green-bg:#EAF5EA;
--purple:#5B5EA6;--purple-bg:#EEEDFE;
--font:system-ui,sans-serif;--mono:'Courier New',monospace;
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
body{font-family:var(--font);background:var(--bg);color:var(--text);font-size:14px;line-height:1.6;}
.doc{max-width:1100px;margin:0 auto;padding:48px 32px 96px;}
hr{border:none;border-top:1px solid var(--border);margin:48px 0;}
/* Header */
.hdr{background:var(--navy);color:#fff;padding:32px 32px 28px;border-radius:8px 8px 0 0;}
.hdr h1{font-family:Georgia,serif;font-size:26px;font-weight:400;letter-spacing:-.02em;margin-bottom:8px;}
.hdr-meta{font-family:var(--mono);font-size:11px;color:rgba(255,255,255,.45);margin-top:10px;}
.badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600;letter-spacing:.05em;background:var(--mint);color:var(--navy);}
.badge-g{background:rgba(255,255,255,.15);color:rgba(255,255,255,.9);}
.badges{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px;}
.decision-box{background:#fff;border:1px solid var(--border);border-top:none;border-radius:0 0 6px 6px;padding:20px 28px 24px;margin-bottom:40px;}
.decision-box h2{font-family:Georgia,serif;font-size:16px;font-weight:400;color:var(--navy);margin-bottom:8px;}
.prose{font-size:13px;color:var(--muted);line-height:1.65;max-width:720px;margin-bottom:10px;}
.prose:last-child{margin-bottom:0;}
/* Sections */
.sec{margin-bottom:52px;}
.sec-label{font-size:10px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);padding-bottom:8px;border-bottom:1px solid var(--border);margin-bottom:22px;}
.sec-title{font-family:Georgia,serif;font-size:20px;font-weight:400;color:var(--navy);margin-bottom:4px;}
.sec-sub{font-size:13px;color:var(--muted);margin-bottom:16px;}
/* Tags */
.tag-list{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px;}
.tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600;letter-spacing:.04em;}
.t-g{background:var(--green-bg);color:var(--green);}
.t-o{background:var(--orange-bg);color:var(--orange);}
.t-n{background:rgba(0,40,80,.08);color:var(--navy);}
.t-p{background:var(--purple-bg);color:var(--purple);}
/* Pipeline diagram */
.pipeline{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:14px 18px;background:#fff;border:1px solid var(--border);border-radius:6px;margin-bottom:24px;}
.pipe-node{text-align:center;}
.pipe-badge{display:inline-block;padding:3px 10px;border-radius:4px;font-size:11px;font-weight:600;margin-bottom:4px;}
.pipe-badge.n1{background:rgba(0,40,80,.08);color:var(--navy);}
.pipe-badge.n2{background:rgba(0,40,80,.08);color:var(--navy);}
.pipe-badge.n3{background:rgba(0,40,80,.08);color:var(--navy);}
.pipe-badge.done{background:var(--green-bg);color:var(--green);}
.pipe-sub{font-size:10px;color:var(--muted);}
.pipe-arrow{font-size:16px;color:var(--border);flex-shrink:0;}
.pipe-col-label{font-size:9px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-top:4px;}
.pipe-col-label.s{color:var(--navy);}
.pipe-col-label.t{color:var(--navy);}
.pipe-col-label.l{color:var(--green);}
/* Column definition grid */
.col-defs{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:28px;}
.col-def{background:#fff;border:1px solid var(--border);border-radius:6px;padding:14px;}
.col-def-title{font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-bottom:6px;}
.col-def-title.n{color:var(--navy);}
.col-def-title.g{color:var(--green);}
.col-def p{font-size:12px;color:var(--muted);line-height:1.5;margin-bottom:8px;}
.col-def code{font-family:var(--mono);font-size:10px;background:rgba(0,40,80,.06);padding:1px 4px;border-radius:2px;}
/* Callout */
.callout{display:flex;gap:12px;padding:14px 16px;border-radius:4px;margin-bottom:16px;font-size:12px;line-height:1.55;}
.callout.orange{background:var(--orange-bg);border-left:3px solid var(--orange);}
.callout.green{background:var(--green-bg);border-left:3px solid var(--green);}
.callout.navy{background:rgba(0,40,80,.05);border-left:3px solid var(--navy);}
.callout.purple{background:var(--purple-bg);border-left:3px solid var(--purple);}
.callout strong{font-weight:700;}
.callout strong.o{color:var(--orange);}
.callout strong.g{color:var(--green);}
.callout strong.n{color:var(--navy);}
.callout strong.p{color:var(--purple);}
/* Sorting options */
.sort-options{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-bottom:20px;}
.sort-opt{background:#fff;border:1px solid var(--border);border-radius:6px;padding:14px;position:relative;}
.sort-opt.rec{border-color:var(--navy);box-shadow:0 0 0 1px var(--navy);}
.sort-opt-rec-badge{position:absolute;top:-8px;right:10px;background:var(--navy);color:#fff;font-size:9px;font-weight:700;padding:2px 8px;border-radius:4px;letter-spacing:.05em;}
.sort-opt h4{font-size:12px;font-weight:700;color:var(--navy);margin-bottom:6px;}
.sort-opt p{font-size:11px;color:var(--muted);line-height:1.5;margin-bottom:8px;}
.sort-opt code{font-family:var(--mono);font-size:10px;background:rgba(0,40,80,.06);padding:1px 4px;border-radius:2px;display:block;margin-top:6px;line-height:1.6;}
/* Frames */
.frames-row{display:flex;gap:24px;flex-wrap:wrap;align-items:flex-start;margin-bottom:16px;}
.caption{font-family:var(--mono);font-size:10px;color:var(--muted);display:block;margin-top:6px;}
/* Desktop frame */
.frame-desktop{background:var(--surface);border-radius:8px;overflow:hidden;border:1px solid var(--border);box-shadow:0 4px 16px rgba(0,0,0,.08);}
.f-nav{height:26px;background:var(--navy);display:flex;align-items:center;padding:0 8px;gap:5px;}
.f-logo{font-size:6.5px;font-weight:700;color:#fff;letter-spacing:.7px;border-bottom:1px solid var(--mint);padding-bottom:1px;}
.f-navlinks{display:flex;gap:5px;margin-left:8px;}
.f-navlink{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:600;text-transform:uppercase;}
.f-navlink.on{color:rgba(255,255,255,.9);}
.f-navr{margin-left:auto;}
.f-av{width:14px;height:14px;border-radius:50%;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-size:4.5px;font-weight:800;color:rgba(255,255,255,.5);}
.f-body{padding:10px;}
.f-search{background:#fff;border:1px solid var(--border);border-radius:4px;height:24px;display:flex;align-items:center;padding:0 8px;gap:5px;margin-bottom:5px;}
.f-si{font-size:9px;color:var(--muted);}
.f-st{font-size:7.5px;color:var(--subtle);flex:1;}
.f-resume{background:var(--mint);opacity:.2;height:7px;border-radius:3px;margin-bottom:8px;}
.f-grid-2{display:grid;grid-template-columns:1fr 155px;gap:7px;margin-bottom:7px;}
.f-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;}
.f-card{background:#fff;border:1px solid var(--sand);border-radius:3px;padding:7px;}
.f-ht{font-size:6px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:5px;}
.f-ht.o{color:var(--orange);}
.f-ht.g{color:var(--green);}
.f-ht.n{color:var(--navy);}
.f-row{border-bottom:1px solid var(--sand);padding:3px 0;}
.f-row:last-of-type{border-bottom:none;}
.f-dn{font-family:Georgia,serif;font-size:7.5px;color:var(--navy);line-height:1.3;}
.f-ds{font-size:6px;color:var(--muted);margin-top:1px;}
.f-dd{font-size:5.5px;color:var(--subtle);margin-left:auto;white-space:nowrap;flex-shrink:0;padding-top:1px;}
.f-lnk{font-size:6px;color:var(--navy);display:block;margin-top:5px;}
.f-lnk.g{color:var(--green);}
.f-stat{font-size:5.5px;color:var(--muted);margin-top:5px;}
.f-dz{border:1.5px dashed var(--mint);background:rgba(166,218,216,.07);border-radius:3px;padding:7px;text-align:center;}
.f-dz-i{font-size:12px;color:var(--navy);opacity:.35;margin-bottom:2px;}
.f-dz-t{font-size:6px;font-weight:700;color:var(--navy);}
.f-dz-s{font-size:5px;color:var(--muted);}
.rhs{display:flex;flex-direction:column;gap:6px;}
/* Strip columns */
.strip-col{border-radius:3px;padding:6px;display:flex;flex-direction:column;gap:4px;}
.strip-col.seg{background:rgba(0,40,80,.03);border:1px solid var(--sand);}
.strip-col.trans{background:rgba(0,40,80,.03);border:1px solid var(--sand);}
.strip-col.done{background:rgba(166,218,216,.10);border:1px solid var(--mint);}
.strip-col.done-empty{background:rgba(166,218,216,.06);border:1.5px dashed var(--mint);align-items:center;justify-content:center;text-align:center;min-height:100px;}
/* Skill pill */
.skill-pill{display:inline-flex;align-items:center;padding:1px 5px;border-radius:8px;font-size:5px;font-weight:700;margin-bottom:3px;}
.skill-pill.easy{background:var(--green-bg);border:1px solid rgba(46,110,57,.2);color:var(--green);}
.skill-pill.kurrent{background:rgba(0,40,80,.08);border:1px solid rgba(0,40,80,.15);color:var(--navy);}
/* Pulse */
.pulse{display:flex;align-items:center;gap:4px;margin-bottom:3px;}
.pulse-num{font-size:5.5px;font-weight:700;}
.pulse-num.g{color:var(--green);}
.pulse-num.n{color:var(--navy);}
.pulse-open{font-size:5px;color:var(--muted);}
/* Avatars */
.avatars{display:flex;gap:2px;margin-bottom:4px;}
.av-sm{width:10px;height:10px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:4px;font-weight:700;color:#fff;}
.av-more{font-size:5px;color:var(--muted);line-height:10px;margin-left:2px;}
/* Per-doc bar */
.doc-bar-row{display:flex;flex-direction:column;gap:2px;border-bottom:1px solid var(--sand);padding-bottom:4px;}
.doc-bar-row:last-child{border-bottom:none;}
.bar-track{flex:1;height:3px;background:rgba(0,40,80,.12);border-radius:2px;overflow:hidden;}
.bar-fill{height:100%;background:var(--navy);border-radius:2px;}
.bar-label{font-size:5px;color:var(--muted);white-space:nowrap;}
/* CTA button */
.cta-btn{display:block;font-size:6px;font-weight:700;color:#fff;background:var(--navy);border-radius:2px;padding:3px 6px;text-align:center;margin-top:3px;}
.cta-btn.ghost{background:transparent;color:var(--navy);border:1px solid var(--navy);}
/* Expert badge */
.expert-badge{display:inline-flex;align-items:center;gap:2px;padding:1px 4px;border-radius:3px;font-size:5px;font-weight:700;background:var(--purple-bg);color:var(--purple);border:1px solid rgba(91,94,166,.2);margin-left:3px;}
/* Phone frame */
.frame-phone{width:200px;flex-shrink:0;background:var(--surface);border-radius:24px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.12),0 0 0 1px rgba(0,0,0,.06);display:flex;flex-direction:column;border:4px solid #1C1C18;}
.ph-nav{height:20px;background:var(--navy);display:flex;align-items:center;padding:0 6px;}
.ph-logo{font-size:5.5px;font-weight:700;color:#fff;letter-spacing:.6px;border-bottom:1px solid var(--mint);padding-bottom:1px;}
.ph-body{flex:1;overflow:hidden;padding:6px;display:flex;flex-direction:column;gap:4px;}
.ph-search{background:#fff;border:1px solid var(--border);border-radius:3px;height:18px;display:flex;align-items:center;padding:0 6px;}
.ph-st{font-size:6.5px;color:var(--subtle);flex:1;}
/* impl-ref */
.impl-ref{margin-top:20px;}
.impl-ref table{width:100%;border-collapse:collapse;font-size:12px;}
.impl-ref th{background:var(--navy);color:#fff;padding:6px 10px;text-align:left;font-size:10px;font-weight:600;letter-spacing:.06em;}
.impl-ref td{padding:7px 10px;border-bottom:1px solid var(--border);vertical-align:top;}
.impl-ref tr:nth-child(even) td{background:var(--surface);}
.impl-ref code{font-family:var(--mono);font-size:11px;background:rgba(0,40,80,.06);padding:1px 4px;border-radius:2px;}
/* Component list */
.comp-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px;}
.comp-card{background:#fff;border:1px solid var(--border);border-radius:6px;padding:14px;}
.comp-card h4{font-size:12px;font-weight:700;color:var(--navy);margin-bottom:4px;}
.comp-card p{font-size:11px;color:var(--muted);line-height:1.5;}
.comp-card code{font-family:var(--mono);font-size:10px;background:rgba(0,40,80,.06);padding:1px 4px;border-radius:2px;}
</style>
</head>
<body>
<div class="doc">
<!-- ── HEADER ───────────────────────────────────────────────────────── -->
<div class="hdr">
<h1>Mission-Control-Streifen — Finale Spec</h1>
<div class="badges">
<span class="badge">Issue #240</span>
<span class="badge badge-g">Leonie Voss — UX &amp; Accessibility</span>
<span class="badge badge-g">15. April 2026</span>
<span class="badge badge-g">v3 — Final</span>
</div>
<div class="hdr-meta">src/routes/+page.svelte · src/lib/components/DashboardMissionControl.svelte · +page.server.ts</div>
</div>
<div class="decision-box">
<h2>Entscheidung</h2>
<p class="prose">
Der bestehende Dashboard-Aufbau (Neueste Aktivität links, DropZone + Metadaten-Widget rechts) bleibt unverändert.
Unterhalb des Zwei-Spalten-Gitters erscheint ein neuer vollbreiter <strong>Mission-Control-Streifen</strong> mit drei
gleichwertigen Spalten: <em>Rahmen einzeichnen</em> (Segmentierung, kein Vorwissen nötig),
<em>Text eintippen</em> (Transkription, Kurrent hilfreich), <em>Lesefertig ✓</em> (Belohnungsbereich).
</p>
<p class="prose">
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 <em>Experten-gesucht</em>-Escape-Hatch verhindert, dass schwer lesbare
Dokumente dauerhaft die Spalte blockieren.
</p>
</div>
<!-- ── PIPELINE ─────────────────────────────────────────────────────── -->
<div class="sec">
<div class="sec-label">Dokument-Lebenszyklus</div>
<div class="pipeline">
<div class="pipe-node">
<div class="pipe-badge n1">Kein Segment</div>
<div class="pipe-sub">0 Annotationen</div>
<div class="pipe-col-label s">→ Spalte 1</div>
</div>
<div class="pipe-arrow"></div>
<div class="pipe-node">
<div class="pipe-badge n2">Segmentiert</div>
<div class="pipe-sub">Rahmen da, wenig Text</div>
<div class="pipe-col-label t">→ Spalte 2</div>
</div>
<div class="pipe-arrow"></div>
<div class="pipe-node">
<div class="pipe-badge n3">In Review</div>
<div class="pipe-sub">Text da, reviewed &lt; 90 %</div>
<div class="pipe-col-label t">→ Spalte 2</div>
</div>
<div class="pipe-arrow"></div>
<div class="pipe-node">
<div class="pipe-badge done">Lesefertig ✓</div>
<div class="pipe-sub">reviewed ≥ 90 %</div>
<div class="pipe-col-label l">→ Spalte 3</div>
</div>
<div style="margin-left:auto;font-size:11px;color:var(--muted);max-width:200px;line-height:1.4;">
„Segmentiert" und „In Review" landen beide in Spalte 2 —
unterschieden durch den per-Dokument-Balken (0 Blöcke vs. N Blöcke).
</div>
</div>
<!-- Column definitions -->
<div class="col-defs">
<div class="col-def">
<div class="col-def-title n">Spalte 1 — Rahmen einzeichnen</div>
<p>Dokumente ohne Annotationsrahmen. Kein Kurrent nötig — Textblöcke markieren reicht.</p>
<p><strong>Bedingung:</strong> <code>annotation_count = 0</code></p>
<p><strong>Sort:</strong> Wöchentliche Rotation (seeded shuffle, s. u.)</p>
<p><strong>Fortschritt:</strong> Wochenpuls „↑ +5 diese Woche", kein globaler Balken</p>
</div>
<div class="col-def">
<div class="col-def-title n">Spalte 2 — Text eintippen</div>
<p>Annotationen vorhanden, aber Text fehlt oder reviewed &lt; 90 %. Kurrent-Kenntnisse hilfreich.</p>
<p><strong>Bedingung:</strong> <code>annotation_count &gt; 0 AND reviewed_pct &lt; 0.90</code></p>
<p><strong>Sort:</strong> Teilfortschritt zuerst, dann wöchentliche Rotation; <code>needsExpert</code>-Flagge schiebt nach hinten</p>
<p><strong>Fortschritt:</strong> Per-Dokument-Balken „3 / 8 Blöcke"</p>
</div>
<div class="col-def" style="background:rgba(166,218,216,.06);border-color:var(--mint);">
<div class="col-def-title g">Spalte 3 — Lesefertig ✓</div>
<p>Reviewed ≥ 90 %. Keine Aufgabe — Einladung zum Lesen.</p>
<p><strong>Bedingung:</strong> <code>reviewed_pct &gt;= 0.90</code></p>
<p><strong>Sort:</strong> Neueste zuerst</p>
<p><strong>Fortschritt:</strong> „94 % geprüft" als Text — kein Balken, die mint-Spalte ist das Signal</p>
<p><strong>Leerstand:</strong> Cross-Column-Redirect zu Spalte 1</p>
</div>
</div>
</div>
<hr/>
<!-- ── HARD DOCUMENTS PROBLEM ─────────────────────────────────────────── -->
<div class="sec">
<div class="sec-label">Sortierstrategie — Das „zu schwer"-Problem</div>
<div class="sec-title">Schwer lesbare Dokumente blockieren die Spalte</div>
<div class="sec-sub">Wenn dieselben 3 Dokumente immer oben stehen und niemand sie lesen kann, stoppt die Transkription komplett.</div>
<div class="callout orange">
<div><strong class="o">Problem:</strong> Bei 1 500 Dokumenten ohne Transkription und sortiert nach <code>updated_at</code>
können dieselben 3 besonders schwer lesbaren Dokumente dauerhaft die Spalte blockieren.
Jeder öffnet sie, gibt auf, und die Spalte wird zur Sackgasse.</div>
</div>
<div class="sort-options">
<!-- Option 1 -->
<div class="sort-opt">
<h4>Option 1 — Zufällig pro Seitenaufruf</h4>
<p><code>ORDER BY RANDOM()</code></p>
<p>Jeder Besuch zeigt andere Dokumente. Kein Aufwand, aber chaotisch — kein Nutzer sieht ein Dokument zweimal,
kann nicht gezielt zurückkehren.</p>
<div class="tag-list"><span class="tag t-g">+ Null Aufwand</span><span class="tag t-o"> Chaotisch</span><span class="tag t-o"> Kein stabiles Lesezeichen</span></div>
</div>
<!-- Option 2 — RECOMMENDED -->
<div class="sort-opt rec">
<div class="sort-opt-rec-badge">★ Empfohlen</div>
<h4>Option 2 — Teilfortschritt + wöchentliche Rotation</h4>
<p>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.</p>
<code>ORDER BY textedBlocks DESC,
HASHTEXT(id || EXTRACT(WEEK FROM NOW())::text)</code>
<div class="tag-list" style="margin-top:8px;"><span class="tag t-g">+ Konsistent innerhalb einer Woche</span><span class="tag t-g">+ Bringt leichte Dokumente an die Oberfläche</span><span class="tag t-g">+ Kein neues Datenbankfeld</span></div>
</div>
<!-- Option 3 -->
<div class="sort-opt">
<h4>Option 3 — Manuelle Schwierigkeitsbewertung</h4>
<p>Beitragende bewerten Dokumente 13 nach Versuch. Einfache Dokumente erscheinen zuerst.</p>
<p>Beste Langzeitlösung — braucht aber Bewertungs-UI auf der Enrich-Seite und Signalakkumulation.</p>
<div class="tag-list"><span class="tag t-g">+ Selbstverbessernd</span><span class="tag t-o"> UI-Aufwand</span><span class="tag t-o"> Braucht Zeit bis Signal</span></div>
</div>
</div>
<!-- Escape hatch -->
<div class="callout navy">
<div>
<strong class="n">Escape-Hatch: „Experten gesucht"-Flagge (Option 2 ergänzen)</strong><br/>
Im Enrich-Bereich: ein einzelner Button „Zu schwer — Hilfe gesucht".
Setzt <code>Document.needsExpert = true</code> (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.
</div>
</div>
<!-- Expert badge mockup -->
<div style="background:#fff;border:1px solid var(--border);border-radius:6px;padding:16px;margin-bottom:16px;">
<div style="font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);margin-bottom:10px;">Mockup: Experten-gesucht-Badge in der Transkriptions-Zeile</div>
<div style="display:flex;flex-direction:column;gap:4px;max-width:380px;">
<!-- Normal doc -->
<div style="display:flex;flex-direction:column;gap:3px;padding:8px;border:1px solid var(--sand);border-radius:3px;">
<div style="font-family:Georgia,serif;font-size:13px;color:var(--navy);">Reisepass Opa Heinrich <span style="font-family:system-ui;font-size:10px;font-weight:600;background:rgba(0,40,80,.07);color:var(--navy);padding:1px 6px;border-radius:4px;">3 / 8 Blöcke</span></div>
<div style="display:flex;align-items:center;gap:6px;">
<div style="flex:1;height:4px;background:rgba(0,40,80,.12);border-radius:2px;overflow:hidden;"><div style="width:37%;height:100%;background:var(--navy);border-radius:2px;"></div></div>
<div style="font-size:11px;color:var(--muted);">37 %</div>
</div>
</div>
<!-- Expert-needed doc -->
<div style="display:flex;flex-direction:column;gap:3px;padding:8px;border:1px solid rgba(91,94,166,.25);background:rgba(91,94,166,.03);border-radius:3px;">
<div style="font-family:Georgia,serif;font-size:13px;color:var(--navy);">Standesamt Breslau 1872
<span style="font-family:system-ui;font-size:10px;font-weight:600;background:var(--purple-bg);color:var(--purple);padding:1px 6px;border-radius:4px;border:1px solid rgba(91,94,166,.2);">Experten gesucht</span>
</div>
<div style="font-size:11px;color:var(--muted);">Schrift besonders schwer lesbar — Hilfe willkommen</div>
</div>
</div>
</div>
<div class="impl-ref">
<table>
<thead><tr><th>Element</th><th>SQL / Tailwind</th><th>Wert</th><th>Hinweis</th></tr></thead>
<tbody>
<tr><td>Sort Transkription</td><td><code>ORDER BY textedBlocks DESC, HASHTEXT(id::text || EXTRACT(WEEK FROM NOW())::int::text)</code></td><td></td><td>Kein neues Feld nötig; ändert sich automatisch jede Woche</td></tr>
<tr><td><code>needsExpert</code>-Flag</td><td><code>ALTER TABLE documents ADD COLUMN needs_expert BOOLEAN NOT NULL DEFAULT FALSE</code></td><td>Flyway <code>V{n}__add_needs_expert.sql</code></td><td>Flagged Docs ans Ende: <code>ORDER BY needs_expert ASC, ...</code></td></tr>
<tr><td>Experten-Badge</td><td><code>inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-purple-50 border border-purple-200 text-purple-700</code></td><td>Kontrast 6,8:1 ✓</td><td>Nur wenn <code>doc.needsExpert === true</code></td></tr>
<tr><td>„Zu schwer"-Button (Enrich)</td><td><code>text-xs text-gray-400 hover:text-gray-600 underline underline-offset-2</code></td><td></td><td>Unscheinbar — kein roter Knopf, keine Scham</td></tr>
<tr><td>Endpoint (Flagge setzen)</td><td><code>PATCH /api/documents/{id}/needs-expert</code></td><td><code>@RequirePermission(READ_ALL)</code></td><td>Jeder angemeldete Nutzer darf flaggen</td></tr>
</tbody>
</table>
</div>
</div>
<hr/>
<!-- ── DESKTOP MOCKUP — FILLED STATE ─────────────────────────────────── -->
<div class="sec">
<div class="sec-label">Mockup — Desktop, normaler Zustand</div>
<div class="frames-row">
<div style="flex:1;min-width:0;">
<div class="frame-desktop">
<div class="f-nav">
<div class="f-logo">FAMILIENARCHIV</div>
<div class="f-navlinks"><div class="f-navlink on">Archiv</div><div class="f-navlink">Personen</div><div class="f-navlink">Gespräche</div></div>
<div class="f-navr"><div class="f-av">MR</div></div>
</div>
<div class="f-body">
<div class="f-search"><div class="f-si"></div><div class="f-st">Dokumente durchsuchen…</div></div>
<div class="f-resume"></div>
<!-- Existing grid — unchanged -->
<div class="f-grid-2">
<div class="f-card">
<div class="f-ht">Neueste Aktivität</div>
<div class="f-row" style="display:flex;"><div><div class="f-dn">Brief von Oma Martha, 1943</div><div class="f-ds">Karl Raddatz</div></div><div class="f-dd">12. Apr</div></div>
<div class="f-row" style="display:flex;"><div><div class="f-dn">Taufurkunde Karl Raddatz</div><div class="f-ds">Standesamt</div></div><div class="f-dd">9. Apr</div></div>
<div class="f-row" style="display:flex;"><div><div class="f-dn">Postkarte aus Breslau</div><div class="f-ds">Martha Raddatz</div></div><div class="f-dd">7. Apr</div></div>
<div class="f-row" style="display:flex;"><div><div class="f-dn">Familienfoto Sommer 1952</div><div class="f-ds">Unbekannt</div></div><div class="f-dd">3. Apr</div></div>
<div class="f-stat">47 Dokumente · 12 Personen</div>
</div>
<div class="rhs">
<div class="f-dz"><div class="f-dz-i"></div><div class="f-dz-t">Datei hochladen</div><div class="f-dz-s">Drag &amp; Drop</div></div>
<div class="f-card" style="flex:1;">
<div class="f-ht o">Metadaten fehlen</div>
<div class="f-row"><div class="f-dn">Familienfoto 1952</div><div class="f-ds">Titel fehlt</div></div>
<div class="f-row"><div class="f-dn">Standesamtsurkunde</div><div class="f-ds">Datum fehlt</div></div>
<a class="f-lnk">Alle 5 anzeigen →</a>
</div>
</div>
</div>
<!-- ★ Mission Control Strip -->
<div style="background:#fff;border:1px solid var(--sand);border-radius:3px;padding:8px;">
<div class="f-ht" style="margin-bottom:7px;">Was braucht Aufmerksamkeit?</div>
<div class="f-grid-3">
<!-- Col 1: SEGMENTIERUNG -->
<div class="strip-col seg">
<div>
<div class="f-ht n" style="margin-bottom:2px;">Rahmen einzeichnen</div>
<div class="skill-pill easy">✓ Ohne Vorkenntnisse</div>
<div class="pulse"><span class="pulse-num g">↑ +5 diese Woche</span><span class="pulse-open">· 1 480 offen</span></div>
<div class="avatars">
<div class="av-sm" style="background:var(--navy);">MR</div>
<div class="av-sm" style="background:var(--purple);">TG</div>
<div class="av-sm" style="background:#8C6E3F;">AS</div>
<div class="av-more">+ 2</div>
</div>
</div>
<div class="f-row"><div class="f-dn">Taufurkunde Karl R.</div><div class="f-ds">Noch keine Rahmen</div></div>
<div class="f-row"><div class="f-dn">Standesamt 1889</div><div class="f-ds">Noch keine Rahmen</div></div>
<div class="f-row"><div class="f-dn">Heiratsurkunde 1921</div><div class="f-ds">Noch keine Rahmen</div></div>
<a class="cta-btn">Jetzt einzeichnen →</a>
</div>
<!-- Col 2: TRANSKRIPTION with per-doc bar + expert badge -->
<div class="strip-col trans">
<div>
<div class="f-ht n" style="margin-bottom:2px;">Text eintippen</div>
<div class="skill-pill kurrent">Kurrent hilfreich</div>
<div class="pulse"><span class="pulse-num n">↑ +2 diese Woche</span><span class="pulse-open">· 8 offen</span></div>
<div class="avatars">
<div class="av-sm" style="background:var(--navy);">MR</div>
<div class="av-more">1 Person</div>
</div>
</div>
<!-- Per-document bar — partial progress first -->
<div class="doc-bar-row">
<div class="f-dn">Reisepass Opa Heinrich</div>
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:37%;"></div></div><div class="bar-label">3 / 8 Blöcke</div></div>
</div>
<div class="doc-bar-row">
<div class="f-dn">Brief v. Oma Martha 1943</div>
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:0%;"></div></div><div class="bar-label">0 / 6 Blöcke</div></div>
</div>
<!-- Expert-needed doc — sorted last -->
<div class="doc-bar-row" style="border-color:rgba(91,94,166,.2);background:rgba(91,94,166,.03);padding:2px 3px;">
<div style="display:flex;align-items:center;flex-wrap:wrap;gap:2px;"><div class="f-dn">Standesamt Breslau 1872</div><span class="expert-badge">Experten gesucht</span></div>
<div class="f-ds">Schrift besonders schwer lesbar</div>
</div>
<a class="cta-btn">Jetzt tippen →</a>
</div>
<!-- Col 3: LESEFERTIG — filled -->
<div class="strip-col done">
<div>
<div class="f-ht g" style="margin-bottom:2px;">Lesefertig ✓</div>
<div style="font-size:5.5px;color:var(--green);font-weight:600;margin-bottom:4px;">3 Dokumente bereit</div>
<div class="avatars">
<div class="av-sm" style="background:var(--green);">MR</div>
<div class="av-sm" style="background:var(--purple);">TG</div>
</div>
</div>
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);">
<div class="f-dn">Postkarte aus Breslau 1943</div>
<div style="font-size:5.5px;color:var(--green);font-weight:600;">100 % geprüft</div>
</div>
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);">
<div class="f-dn">Brief Oma Martha 1938</div>
<div style="font-size:5.5px;color:var(--green);font-weight:600;">95 % geprüft</div>
</div>
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);">
<div class="f-dn">Heiratsurkunde 1921</div>
<div style="font-size:5.5px;color:var(--green);font-weight:600;">91 % geprüft</div>
</div>
<a class="f-lnk g" style="margin-top:3px;">Alle 3 lesen →</a>
</div>
</div>
</div>
</div>
</div>
<span class="caption">Desktop (55 %) — normaler Zustand: Teilfortschritt oben, Experten-gesucht-Dokument unten in Spalte 2</span>
</div>
</div>
</div>
<!-- ── DESKTOP MOCKUP — EARLY STATE (Lesefertig leer) ───────────────── -->
<div class="sec">
<div class="sec-label">Mockup — Desktop, frühe Projektphase (Lesefertig leer)</div>
<div class="frames-row">
<div style="flex:1;min-width:0;">
<div class="frame-desktop">
<div class="f-nav">
<div class="f-logo">FAMILIENARCHIV</div>
<div class="f-navlinks"><div class="f-navlink on">Archiv</div><div class="f-navlink">Personen</div></div>
<div class="f-navr"><div class="f-av">MR</div></div>
</div>
<div class="f-body">
<div class="f-search"><div class="f-si"></div><div class="f-st">Dokumente durchsuchen…</div></div>
<div class="f-resume"></div>
<div class="f-grid-2">
<div class="f-card">
<div class="f-ht">Neueste Aktivität</div>
<div class="f-row" style="display:flex;"><div><div class="f-dn">Brief von Oma Martha, 1943</div></div><div class="f-dd">12. Apr</div></div>
<div class="f-row" style="display:flex;"><div><div class="f-dn">Taufurkunde Karl Raddatz</div></div><div class="f-dd">9. Apr</div></div>
<div class="f-stat">1 500 Dokumente · 12 Personen</div>
</div>
<div class="rhs">
<div class="f-dz"><div class="f-dz-i"></div><div class="f-dz-t">Datei hochladen</div><div class="f-dz-s">Drag &amp; Drop</div></div>
<div class="f-card" style="flex:1;">
<div class="f-ht o">Metadaten fehlen</div>
<div class="f-row"><div class="f-dn">Familienfoto 1952</div></div>
<div class="f-row"><div class="f-dn">Standesamtsurkunde</div></div>
<a class="f-lnk">Alle anzeigen →</a>
</div>
</div>
</div>
<div style="background:#fff;border:1px solid var(--sand);border-radius:3px;padding:8px;">
<div class="f-ht" style="margin-bottom:7px;">Was braucht Aufmerksamkeit?</div>
<div class="f-grid-3">
<div class="strip-col seg">
<div>
<div class="f-ht n" style="margin-bottom:2px;">Rahmen einzeichnen</div>
<div class="skill-pill easy">✓ Ohne Vorkenntnisse</div>
<div class="pulse"><span class="pulse-num g">↑ +3 diese Woche</span><span class="pulse-open">· 1 498 offen</span></div>
<div class="avatars"><div class="av-sm" style="background:var(--navy);">MR</div><div class="av-more">1 Person</div></div>
</div>
<div class="f-row"><div class="f-dn">Taufurkunde Karl R.</div></div>
<div class="f-row"><div class="f-dn">Standesamt 1889</div></div>
<div class="f-row"><div class="f-dn">Heiratsurkunde 1921</div></div>
<a class="cta-btn">Jetzt einzeichnen →</a>
</div>
<div class="strip-col trans">
<div>
<div class="f-ht n" style="margin-bottom:2px;">Text eintippen</div>
<div class="skill-pill kurrent">Kurrent hilfreich</div>
<div class="pulse"><span class="pulse-num n">↑ +1 diese Woche</span><span class="pulse-open">· 2 offen</span></div>
<div class="avatars"><div class="av-sm" style="background:var(--navy);">MR</div><div class="av-more">1 Person</div></div>
</div>
<div class="doc-bar-row">
<div class="f-dn">Brief v. Oma Martha 1943</div>
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:0%;"></div></div><div class="bar-label">0 / 6 Blöcke</div></div>
</div>
<div class="doc-bar-row">
<div class="f-dn">Reisepass Opa Heinrich</div>
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:0%;"></div></div><div class="bar-label">0 / 4 Blöcke</div></div>
</div>
<a class="cta-btn">Jetzt tippen →</a>
</div>
<!-- Lesefertig EMPTY — cross-column redirect -->
<div class="strip-col done-empty">
<div style="font-size:11px;color:var(--mint);margin-bottom:3px;"></div>
<div style="font-size:6.5px;font-weight:700;color:var(--navy);margin-bottom:3px;">Noch kein Dokument lesefertig</div>
<div style="font-size:5.5px;color:var(--muted);line-height:1.5;max-width:105px;margin-bottom:5px;">Erscheint hier sobald die Transkription abgeschlossen ist.</div>
<a class="cta-btn ghost" style="font-size:5.5px;padding:2px 7px;">Jetzt mithelfen →</a>
</div>
</div>
</div>
</div>
</div>
<span class="caption">Desktop (55 %) — frühe Phase: 1 500 Dokumente ohne Transkription, Wochenpuls zeigt Schwung statt Berg</span>
</div>
</div>
</div>
<hr/>
<!-- ── MOBILE MOCKUP ─────────────────────────────────────────────────── -->
<div class="sec">
<div class="sec-label">Mockup — Mobil 320 px</div>
<p class="prose" style="margin-bottom:16px;">
Die rechte Spalte (DropZone + Metadaten) erscheint auf Mobil zuerst im DOM (<code>lg:order-last</code> schiebt sie auf Desktop nach rechts).
Der Streifen stapelt seine drei Spalten vertikal. Jede Spalte hat volle Breite — keine Overflow-Probleme.
</p>
<div class="frames-row">
<!-- Phone: filled state -->
<div>
<div class="frame-phone" style="height:620px;">
<div class="ph-nav"><div class="ph-logo">FAMILIENARCHIV</div></div>
<div class="ph-body" style="overflow-y:auto;">
<div class="ph-search"><div class="ph-st">⌕ Dokumente…</div></div>
<!-- Right col first on mobile -->
<div class="f-dz" style="padding:5px;"><div class="f-dz-i" style="font-size:10px;"></div><div class="f-dz-t">Hochladen</div></div>
<div class="f-card" style="padding:5px;">
<div class="f-ht o">Metadaten fehlen</div>
<div class="f-row"><div class="f-dn">Familienfoto 1952</div></div>
<div class="f-row"><div class="f-dn">Standesamtsurkunde</div></div>
</div>
<!-- Left col (recent) -->
<div class="f-card" style="padding:5px;">
<div class="f-ht">Neueste Aktivität</div>
<div class="f-row"><div class="f-dn">Brief von Oma Martha</div></div>
<div class="f-row"><div class="f-dn">Taufurkunde Karl R.</div></div>
<div class="f-stat">1 500 Dok. · 12 Pers.</div>
</div>
<!-- Strip — stacked on mobile -->
<div style="background:#fff;border:1px solid var(--sand);border-radius:3px;padding:5px;display:flex;flex-direction:column;gap:4px;">
<div class="f-ht" style="margin-bottom:3px;">Was braucht Aufmerksamkeit?</div>
<!-- Seg -->
<div class="strip-col seg" style="padding:5px;">
<div class="f-ht n" style="margin-bottom:1px;">Rahmen einzeichnen</div>
<div class="skill-pill easy">✓ Ohne Vorkenntnisse</div>
<div class="pulse" style="margin-bottom:2px;"><span class="pulse-num g">↑ +5 diese Woche</span><span class="pulse-open">· 1 480 offen</span></div>
<div class="f-row"><div class="f-dn">Taufurkunde Karl R.</div></div>
<div class="f-row"><div class="f-dn">Standesamt 1889</div></div>
<a class="cta-btn" style="font-size:6px;">Jetzt einzeichnen →</a>
</div>
<!-- Trans -->
<div class="strip-col trans" style="padding:5px;">
<div class="f-ht n" style="margin-bottom:1px;">Text eintippen</div>
<div class="skill-pill kurrent">Kurrent hilfreich</div>
<div class="pulse" style="margin-bottom:2px;"><span class="pulse-num n">↑ +2 diese Woche</span><span class="pulse-open">· 8 offen</span></div>
<div class="doc-bar-row">
<div class="f-dn">Reisepass Opa Heinrich</div>
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:37%;"></div></div><div class="bar-label">3 / 8 Blöcke</div></div>
</div>
<div class="doc-bar-row">
<div class="f-dn">Brief v. Oma Martha 1943</div>
<div style="display:flex;align-items:center;gap:3px;"><div class="bar-track"><div class="bar-fill" style="width:0%;"></div></div><div class="bar-label">0 / 6 Blöcke</div></div>
</div>
<a class="cta-btn" style="font-size:6px;">Jetzt tippen →</a>
</div>
<!-- Lesefertig -->
<div class="strip-col done" style="padding:5px;">
<div class="f-ht g" style="margin-bottom:1px;">Lesefertig ✓</div>
<div style="font-size:5.5px;color:var(--green);font-weight:600;margin-bottom:3px;">3 bereit</div>
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);"><div class="f-dn">Postkarte 1943</div><div style="font-size:5.5px;color:var(--green);font-weight:600;">100 %</div></div>
<div class="doc-bar-row" style="border-color:rgba(166,218,216,.4);"><div class="f-dn">Brief Oma 1938</div><div style="font-size:5.5px;color:var(--green);font-weight:600;">95 %</div></div>
<a class="f-lnk g">Alle lesen →</a>
</div>
</div>
</div>
</div>
<span class="caption">Mobil 320 px — Streifen stapelt vertikal, volle Breite je Spalte</span>
</div>
<!-- Mobile layout notes -->
<div style="flex:1;min-width:220px;">
<div style="background:#fff;border:1px solid var(--border);border-radius:6px;padding:16px;margin-bottom:12px;">
<div style="font-size:10px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--navy);margin-bottom:8px;">Mobile-Reihenfolge (DOM)</div>
<ol style="font-size:12px;color:var(--muted);line-height:1.8;margin-left:16px;">
<li>Suchleiste</li>
<li>DropZone (write users only)</li>
<li>Metadaten fehlen</li>
<li>Neueste Aktivität</li>
<li>Was braucht Aufmerksamkeit?
<ol style="margin-left:16px;">
<li>Rahmen einzeichnen</li>
<li>Text eintippen</li>
<li>Lesefertig ✓</li>
</ol>
</li>
</ol>
</div>
<div class="callout navy">
<div>
<strong class="n">Touch targets:</strong> Alle CTA-Buttons: <code>min-h-[44px]</code> (WCAG 2.2).
Dokument-Zeilen in den Spalten: <code>min-h-[44px] py-2</code>.
Der „Zu schwer"-Button auf der Enrich-Seite: <code>min-h-[44px]</code> als Icon-Button mit <code>aria-label</code>.
</div>
</div>
</div>
</div>
</div>
<hr/>
<!-- ── ENGAGEMENT FEATURES SUMMARY ──────────────────────────────────── -->
<div class="sec">
<div class="sec-label">Engagement-Elemente — Zusammenfassung</div>
<div class="comp-grid">
<div class="comp-card">
<h4>① Skill-Pill</h4>
<p>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.</p>
<p style="margin-top:6px;"><code>bg-green-50 border-green-200 text-green-800</code> / <code>bg-surface border-line text-ink</code></p>
</div>
<div class="comp-card">
<h4>② Wochenpuls</h4>
<p>„↑ +5 diese Woche · 1 480 offen" statt globalem Fortschrittsbalken.
Zeigt Schwung, nicht den Berg. Psychologisch: 0,8 %-Balken ist demotivierender als kein Balken.</p>
<p style="margin-top:6px;"><code>SELECT COUNT(*) WHERE created_at &gt; NOW() - INTERVAL '7 days'</code></p>
</div>
<div class="comp-card">
<h4>③ Per-Dokument-Balken</h4>
<p>Nur in Spalte 2, nur wenn <code>annotation_count &gt; 0</code>. Richtiger Maßstab:
8 Blöcke sind in einer Sitzung abschließbar. Zeigt auch, welche Dokumente „fast fertig" sind.</p>
<p style="margin-top:6px;"><code>width: {textedBlocks / totalBlocks * 100}%</code>; Guard: <code>totalBlocks === 0 → width: 0</code></p>
</div>
<div class="comp-card">
<h4>④ Contributor-Avatare</h4>
<p>Max. 3 Initialen-Bubbles der letzten Beitragenden pro Spalte. Kein Leaderboard (Wettbewerb) —
soziale Sichtbarkeit (Zugehörigkeit). Farbe deterministisch aus User-ID-Hash.</p>
<p style="margin-top:6px;">DTO: <code>lastContributors: [{initials, colorIndex}]</code> — nur Initialen, keine Namen (Nora)</p>
</div>
<div class="comp-card">
<h4>⑤ „Starte hier →"-CTA</h4>
<p>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.</p>
<p style="margin-top:6px;"><code>/enrich?filter=NEEDS_SEGMENTATION&amp;next=1</code> (Segmentierung)<br/><code>/enrich?filter=NEEDS_TRANSCRIPTION&amp;next=1</code> (Transkription)</p>
</div>
<div class="comp-card">
<h4>⑥ Lesefertig-Leerstand → Redirect</h4>
<p>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.</p>
<p style="margin-top:6px;"><code>{#if readyToRead.length === 0}</code><code>DashboardReadyToReadEmpty.svelte</code></p>
</div>
</div>
</div>
<hr/>
<!-- ── IMPL-REF TABLE ────────────────────────────────────────────────── -->
<div class="sec">
<div class="sec-label">Implementation Reference</div>
<div class="impl-ref">
<table>
<thead><tr><th>Element</th><th>Tailwind-Klassen</th><th>Pixel / Wert</th><th>Hinweis</th></tr></thead>
<tbody>
<tr><td><strong>Streifen-Wrapper</strong></td><td><code>mt-4 bg-white border border-line rounded-sm p-6</code></td><td>padding 24 px</td><td>Direkt nach bestehendem <code>div.mt-4.grid</code></td></tr>
<tr><td>Streifen-Titel</td><td><code>text-xs font-bold uppercase tracking-widest text-gray-400 mb-4</code></td><td>12 px / 700</td><td>Standard-Section-Title-Muster</td></tr>
<tr><td>3-Spalten-Grid</td><td><code>grid grid-cols-1 gap-4 sm:grid-cols-3</code></td><td>gap 16 px</td><td>sm = 640 px; darunter stapeln</td></tr>
<tr><td>Segmentierung-Spalte</td><td><code>bg-surface rounded-sm border border-line p-4 flex flex-col gap-3</code></td><td></td><td>Neutral</td></tr>
<tr><td>Transkription-Spalte</td><td><code>bg-surface rounded-sm border border-line p-4 flex flex-col gap-3</code></td><td></td><td>Neutral — es ist eine Aufgabe</td></tr>
<tr><td>Lesefertig-Spalte (gefüllt)</td><td><code>bg-mint/10 rounded-sm border border-mint p-4 flex flex-col gap-3</code></td><td></td><td>Mint-Ton = Erfolg</td></tr>
<tr><td>Lesefertig-Spalte (leer)</td><td><code>flex flex-col items-center justify-center text-center bg-mint/5 border border-dashed border-mint rounded-sm p-6 min-h-[120px]</code></td><td>min-h 120 px</td><td>Kein toter Endpunkt</td></tr>
<tr><td>Skill-Pill easy</td><td><code>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</code></td><td>Kontrast 9,7:1 ✓ AAA</td><td></td></tr>
<tr><td>Skill-Pill kurrent</td><td><code>inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-surface border border-line text-ink</code></td><td>Kontrast 14,5:1 ✓ AAA</td><td>Neutral — kein Abschreck-Signal</td></tr>
<tr><td>Wochenpuls-Zahl</td><td><code>text-xs font-semibold text-green-700</code> (Seg.) / <code>text-ink</code> (Trans.)</td><td>12 px</td><td>Kein globaler Balken</td></tr>
<tr><td>Per-Dokument-Track</td><td><code>flex-1 h-1 bg-navy/20 rounded-full overflow-hidden</code></td><td>h 4 px</td><td>Nur wenn <code>annotation_count &gt; 0</code></td></tr>
<tr><td>Per-Dokument-Fill</td><td><code>h-full bg-ink rounded-full transition-all</code> + <code>style="width:{pct}%"</code></td><td></td><td>Guard: <code>totalBlocks === 0 → 0%</code></td></tr>
<tr><td>Lesefertig-Prozent</td><td><code>text-xs font-semibold text-green-800</code></td><td>12 px</td><td>Kein Balken — mint-Spalte ist das Signal</td></tr>
<tr><td>Contributor-Avatar</td><td><code>w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold text-white shrink-0</code></td><td>24 × 24 px</td><td>Farbe: 6 Werte, Index = <code>userIdHash % 6</code></td></tr>
<tr><td>CTA-Button (primär)</td><td><code>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</code></td><td>min-h 36 px</td><td><code>aria-label</code> mit Dokumenttitel falls nötig</td></tr>
<tr><td>CTA-Button (ghost, Leerstand)</td><td><code>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</code></td><td>min-h 36 px</td><td></td></tr>
<tr><td>Experten-gesucht-Badge</td><td><code>inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-purple-50 border border-purple-200 text-purple-700</code></td><td>Kontrast 6,8:1 ✓ AA</td><td>Nur wenn <code>doc.needsExpert === true</code></td></tr>
<tr><td>Sichtbarkeit Streifen</td><td><code>{#if needsSegmentation.length &gt; 0 || needsTranscription.length &gt; 0 || readyToRead.length &gt; 0}</code></td><td></td><td>Streifen verschwindet wenn alle drei Buckets leer</td></tr>
<tr><td>Dokument-Zeile Mindesthöhe</td><td><code>min-h-[44px] flex items-start py-2</code></td><td>44 px ✓ WCAG 2.2</td><td>Gilt für alle klickbaren Zeilen</td></tr>
</tbody>
</table>
</div>
</div>
<hr/>
<!-- ── BACKEND CONTRACTS ─────────────────────────────────────────────── -->
<div class="sec">
<div class="sec-label">Backend — neue Endpoints &amp; Queries</div>
<div class="impl-ref">
<table>
<thead><tr><th>Endpoint / Query</th><th>Bedingung</th><th>Sort</th><th>Auth</th></tr></thead>
<tbody>
<tr><td><code>GET /api/documents/needs-segmentation?size=3</code></td><td><code>NOT EXISTS (SELECT 1 FROM document_annotations WHERE document_id = d.id)</code></td><td><code>HASHTEXT(id::text || week::text)</code></td><td><code>READ_ALL</code></td></tr>
<tr><td><code>GET /api/documents/needs-transcription?size=3</code></td><td><code>EXISTS annotation AND (no blocks OR reviewed_pct &lt; 0.90)</code></td><td><code>textedBlocks DESC, needs_expert ASC, HASHTEXT(...)</code></td><td><code>READ_ALL</code></td></tr>
<tr><td><code>GET /api/documents/ready-to-read?size=3</code></td><td><code>reviewed_pct &gt;= 0.90</code></td><td><code>updated_at DESC</code></td><td><code>READ_ALL</code></td></tr>
<tr><td><code>PATCH /api/documents/{id}/needs-expert</code></td><td>Setzt <code>needs_expert = true</code></td><td></td><td><code>READ_ALL</code> (jeder Nutzer darf flaggen)</td></tr>
<tr><td><code>GET /api/stats/strip-activity</code></td><td>Wochenpuls: <code>COUNT(*) WHERE created_at &gt; NOW() - INTERVAL '7 days'</code> pro Bucket</td><td></td><td><code>READ_ALL</code></td></tr>
<tr><td>Flyway-Migration</td><td><code>ALTER TABLE documents ADD COLUMN needs_expert BOOLEAN NOT NULL DEFAULT FALSE</code></td><td></td><td>V{n}__add_needs_expert_flag.sql</td></tr>
<tr><td>Index prüfen (Tobias)</td><td><code>document_annotations(document_id)</code>, <code>transcription_blocks(document_id, reviewed)</code></td><td></td><td>EXPLAIN ANALYZE vor Merge</td></tr>
<tr><td>Division durch 0 (Sara)</td><td>Alle reviewed_pct-Queries: <code>CASE WHEN COUNT(*) = 0 THEN 0 ELSE SUM(...)::float / COUNT(*) END</code></td><td></td><td></td></tr>
</tbody>
</table>
</div>
</div>
<hr/>
<!-- ── NEW COMPONENTS ────────────────────────────────────────────────── -->
<div class="sec">
<div class="sec-label">Neue Svelte-Komponenten</div>
<div class="comp-grid">
<div class="comp-card">
<h4><code>DashboardMissionControl.svelte</code></h4>
<p>Wrapper für den vollbreiten Streifen. Props: <code>needsSegmentation</code>, <code>needsTranscription</code>,
<code>readyToRead</code>, <code>weeklyActivity</code>. Rendert die drei Spalten und ist komplett unsichtbar wenn alle Arrays leer sind.</p>
</div>
<div class="comp-card">
<h4><code>DashboardSegmentationCol.svelte</code></h4>
<p>Spalte 1: Skill-Pill, Wochenpuls, Avatare, Dokumentliste, CTA. Keine Balken — keine Dokument-Metadaten vorhanden.</p>
</div>
<div class="comp-card">
<h4><code>DashboardTranscriptionCol.svelte</code></h4>
<p>Spalte 2: Skill-Pill, Wochenpuls, Avatare, per-Dokument-Balken, Experten-Badge bei <code>needsExpert</code>, CTA.</p>
</div>
<div class="comp-card">
<h4><code>DashboardReadyToReadCol.svelte</code></h4>
<p>Spalte 3: Zeigt gefüllten Zustand (Liste mit %-Text) oder leeren Zustand (Cross-Column-Redirect zu Segmentierung).</p>
</div>
</div>
<div class="callout green">
<div>
<strong class="g">Bestehende Komponente bleibt:</strong> <code>DashboardNeedsMetadata.svelte</code> ist unverändert —
sie lebt weiterhin in der rechten Spalte. Der Mission-Control-Streifen ist vollständig additiv und ändert nichts am bestehenden Layout.
</div>
</div>
</div>
</div><!-- /doc -->
</body>
</html>