Files
familienarchiv/docs/specs/annotation-transcription-final-spec.html
Marcel 46d64f50a5
Some checks failed
CI / Unit & Component Tests (push) Failing after 1m52s
CI / Backend Unit Tests (push) Failing after 2m54s
CI / E2E Tests (push) Failing after 1h13m6s
docs(specs): add final specs for transcription feature
Three final UI/UX specs for the collaborative transcription system:
- expandable-metadata-header-spec: labeled "Details" toggle with drawer
- annotation-transcription-final-spec: annotation-backed transcription with block-level comment threads
- transcription-read-mode-final-spec: clean split read mode with flowing prose and scroll sync

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

964 lines
75 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="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Annotation-Backed Transcription — Final Spec</title>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
<style>
:root{--color-page:#FAFAF7;--color-surface:#F5F4EE;--color-subtle:#EDECEA;--color-border:#D8D7D0;--color-text-muted:#6B6A63;--color-text:#1C1C18;--navy:#012851;--mint:#A1DCD8;--sand:#F0EFE9;--turquoise:#00C7B1;--accent-bg:rgba(161,220,216,.12);--blue-tint:#E6F1FB;--blue:#2D7DD2;--blue-dark:#185FA5;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--green-tint:#E8F5EA;--green:#3D8C4A;--green-dark:#2E6E39;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--yellow-tint:#FDF6D8;--yellow-text:#8A6800;--color-error:#DC4C3E;--font-display:'Fraunces',Georgia,serif;--font-sans:'DM Sans',system-ui,sans-serif;--font-mono:'DM Mono',monospace;--radius-sm:4px;--radius-md:6px;--radius-lg:10px;--radius-xl:16px;--shadow-card:0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);--shadow-raised:0 4px 12px rgba(28,28,24,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
.doc-header{display:flex;justify-content:space-between;align-items:flex-end;padding-bottom:28px;border-bottom:1px solid var(--color-border);margin-bottom:48px;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
.doc-header p{font-size:13px;color:var(--color-text-muted);max-width:680px;}
.doc-meta{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);text-align:right;line-height:1.9;}
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;}
.pill-g{background:var(--green-tint);color:var(--green-dark);}
.section{margin-bottom:64px;}
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
.jh-b{background:var(--blue-tint);border:1px solid #A4CFF4;}.jh-b .jn{color:var(--blue);}.jh-b p,.jh-b .fl{color:var(--blue-dark);}
.jh-g{background:var(--green-tint);border:1px solid #A0D8A8;}.jh-g .jn{color:var(--green);}.jh-g p,.jh-g .fl{color:var(--green-dark);}
.jh-o{background:var(--orange-tint);border:1px solid #F0C89A;}.jh-o .jn{color:var(--orange);}.jh-o p,.jh-o .fl{color:var(--orange-dark);}
.scr{margin-bottom:56px;}
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;flex-direction:column;min-height:520px;}
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
/* ── FA chrome ── */
.fa-nav{height:32px;background:var(--navy);display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0;}
.fa-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid var(--mint);padding-bottom:1px;}
.fa-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;}
.fa-nav-r{margin-left:auto;display:flex;gap:5px;align-items:center;}
.fa-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5);}
.fa-topbar{background:#fff;border-bottom:1px solid #e4e2d7;display:flex;align-items:center;padding:0 12px;gap:6px;height:42px;flex-shrink:0;}
.fa-topbar .back{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;color:var(--color-text-muted);}
.fa-topbar .title{font-family:Georgia,serif;font-size:11px;color:var(--navy);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.fa-chip{display:inline-flex;align-items:center;gap:2px;padding:1px 5px 1px 2px;background:var(--sand);border:1px solid #e4e2d7;border-radius:8px;white-space:nowrap;font-size:7px;color:var(--color-text);}
.fa-chip .av{width:12px;height:12px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;flex-shrink:0;}
.fa-chip .av.navy{background:var(--navy);color:var(--mint);}
.fa-chip .av.purple{background:#5A3080;color:#fff;}
.fa-topbar-btn{font-size:7px;font-weight:600;padding:3px 8px;border-radius:4px;border:1px solid var(--navy);color:var(--navy);background:transparent;display:flex;align-items:center;gap:3px;}
.fa-topbar-btn.active{background:var(--navy);color:#fff;border-color:var(--navy);}
.fa-topbar-btn.ghost{border-color:var(--color-border);color:var(--color-text-muted);font-weight:500;}
.fa-topbar-btn.transcribe{background:var(--turquoise);color:var(--navy);border-color:var(--turquoise);font-weight:700;}
.details-toggle{display:inline-flex;align-items:center;gap:3px;padding:2px 8px 2px 6px;border-radius:4px;font-size:7px;font-weight:600;color:var(--color-text-muted);cursor:pointer;border:1px solid var(--color-border);background:transparent;white-space:nowrap;}
/* ── PDF + paper ── */
.pdf-area{background:#D4D0C8;flex:1;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;}
.paper{background:#FFFEF8;box-shadow:0 2px 8px rgba(0,0,0,.14);border-radius:1px;padding:9px 11px;display:flex;flex-direction:column;gap:2px;position:relative;}
.pl{height:3px;background:#C4BDB0;border-radius:1px;opacity:.5;margin-bottom:2px;}
.ps{height:2px;background:#C4BDB0;border-radius:1px;opacity:.28;margin-bottom:1.5px;}
/* ── Annotation rectangles on PDF ── */
.ann-rect{position:absolute;border-radius:2px;pointer-events:auto;cursor:pointer;transition:all .15s ease;}
.ann-rect.comment{border:1.5px solid rgba(255,200,0,.6);background:rgba(255,200,0,.15);}
.ann-rect.comment:hover{background:rgba(255,200,0,.3);}
.ann-rect.trans{border:1.5px solid var(--turquoise);background:rgba(0,199,177,.1);}
.ann-rect.trans:hover{background:rgba(0,199,177,.2);}
.ann-rect.trans.active{background:rgba(0,199,177,.25);box-shadow:0 0 0 2px var(--turquoise);}
.ann-rect .ann-num{position:absolute;top:-8px;left:-8px;width:16px;height:16px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:7px;font-weight:700;color:#fff;box-shadow:0 1px 3px rgba(0,0,0,.3);}
.ann-rect.trans .ann-num{background:var(--navy);}
.ann-rect.comment .ann-num{background:var(--orange);}
.ann-rect .ann-badge{position:absolute;bottom:-8px;right:-8px;background:var(--navy);color:#fff;font-size:6px;font-weight:700;padding:1px 4px;border-radius:8px;min-width:14px;text-align:center;box-shadow:0 1px 2px rgba(0,0,0,.3);}
/* ── Split + panels ── */
.split{display:flex;flex:1;overflow:hidden;}
.split-left{flex:1;display:flex;flex-direction:column;overflow:hidden;position:relative;}
.split-right{display:flex;flex-direction:column;overflow:hidden;border-left:1px solid #e4e2d7;}
.split-handle{width:4px;background:var(--color-border);cursor:col-resize;flex-shrink:0;display:flex;align-items:center;justify-content:center;}
.split-handle::after{content:'';width:2px;height:20px;background:var(--color-text-muted);border-radius:1px;opacity:.3;}
/* ── Transcript blocks ── */
.tblock{margin-bottom:6px;border:1px solid var(--color-border);border-radius:5px;overflow:hidden;transition:all .15s ease;}
.tblock.active{border-color:var(--turquoise);box-shadow:0 0 0 1px var(--turquoise);}
.tblock.empty{border-style:dashed;opacity:.7;}
.tblock-head{display:flex;align-items:center;gap:4px;padding:3px 8px;font-size:6px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--color-text-muted);}
.tblock-head.active-bg{background:rgba(0,199,177,.08);}
.tblock-head .num{width:14px;height:14px;border-radius:50%;background:var(--navy);color:#fff;display:flex;align-items:center;justify-content:center;font-size:6px;font-weight:700;flex-shrink:0;}
.tblock-body{padding:5px 8px;font-family:Georgia,serif;font-size:9px;line-height:1.65;color:var(--color-text);min-height:18px;}
.tblock-body.editing{background:var(--color-page);cursor:text;}
.tblock-body .illegible{color:var(--color-text-muted);font-style:italic;}
.tblock-footer{display:flex;align-items:center;gap:4px;padding:2px 8px;border-top:1px solid var(--color-subtle);font-size:6px;color:var(--color-text-muted);}
.trans-cursor{display:inline-block;width:1px;height:10px;background:var(--blue);animation:blink 1s infinite;margin-left:1px;}
@keyframes blink{0%,50%{opacity:1}51%,100%{opacity:0}}
/* ── Presence ── */
.presence{display:flex;align-items:center;gap:3px;font-size:7px;color:var(--color-text-muted);}
.presence-dot{width:5px;height:5px;border-radius:50%;}
.hl-blue{border-left:2px solid var(--blue);padding-left:6px;background:rgba(45,125,210,.04);}
.hl-purple{border-left:2px solid var(--purple);padding-left:6px;background:rgba(83,74,183,.04);}
/* ── Inline comment thread ── */
.inline-thread{margin:3px 8px 5px;padding:5px 8px;border-radius:4px;border-left:2px solid var(--orange);background:var(--orange-tint);font-size:8px;color:var(--color-text);}
.inline-thread .thread-head{font-size:6px;font-weight:600;color:var(--orange-dark);margin-bottom:2px;display:flex;align-items:center;gap:3px;}
.inline-thread .thread-msg{display:flex;gap:3px;align-items:flex-start;margin-bottom:2px;}
.inline-thread .thread-av{width:12px;height:12px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:#fff;flex-shrink:0;}
.inline-thread .thread-reply{display:flex;gap:3px;margin-top:3px;}
.inline-thread input{flex:1;font-size:7px;padding:2px 5px;border:1px solid var(--color-border);border-radius:3px;background:#fff;}
.inline-thread .resolve-btn{font-size:6px;font-weight:600;color:var(--green-dark);padding:2px 5px;cursor:pointer;}
/* ── Hint strip ── */
.hint-strip{display:flex;align-items:center;gap:6px;padding:0 12px;height:22px;border-top:1px dashed;flex-shrink:0;font-size:7px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;}
.hint-strip.trans-hint{background:rgba(0,199,177,.06);border-color:rgba(0,199,177,.3);color:var(--navy);}
.hint-strip .hint-step{display:flex;align-items:center;gap:3px;font-weight:500;color:var(--color-text-muted);text-transform:none;letter-spacing:0;}
/* ── Transcript toolbar ── */
.trans-toolbar{background:#fff;border-bottom:1px solid #e4e2d7;display:flex;align-items:center;padding:4px 8px;gap:6px;flex-shrink:0;}
.trans-toolbar-btn{font-size:6px;font-weight:600;padding:2px 6px;border-radius:3px;border:1px solid var(--color-border);color:var(--color-text-muted);background:transparent;cursor:pointer;display:flex;align-items:center;gap:2px;}
.trans-toolbar-btn:hover{background:var(--sand);color:var(--color-text);}
.trans-toolbar-btn.active{background:var(--navy);color:#fff;border-color:var(--navy);}
/* ── History panel (in-toolbar) ── */
.history-panel{background:var(--color-page);border:1px solid var(--color-border);border-radius:5px;margin:4px 8px;padding:6px 8px;font-size:7px;}
.history-entry{display:flex;align-items:center;gap:4px;padding:3px 0;border-bottom:1px solid var(--color-subtle);}
.history-entry:last-child{border-bottom:none;}
.history-entry .he-date{font-size:6px;color:var(--color-text-muted);min-width:40px;}
.history-entry .he-user{font-size:6px;font-weight:600;color:var(--color-text);min-width:40px;}
.history-entry .he-diff{font-size:7px;color:var(--color-text);}
.he-add{background:var(--green-tint);color:var(--green-dark);padding:0 2px;border-radius:1px;}
.he-del{background:#FEE2E2;color:#991B1B;padding:0 2px;border-radius:1px;text-decoration:line-through;}
/* ── Status bar ── */
.status-bar{background:var(--sand);border-top:1px solid #e4e2d7;height:18px;display:flex;align-items:center;padding:0 8px;font-size:7px;color:var(--color-text-muted);gap:8px;flex-shrink:0;}
.status-saved{color:var(--green-dark);}
/* ── Agent table ── */
.agent{background:var(--color-text);color:#E8E8E2;padding:24px;border-radius:var(--radius-lg);margin-top:20px;}
.agent h4{font-size:9px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#5A5A55;margin-bottom:12px;}
.agent pre{font-family:var(--font-mono);font-size:10px;color:#444440;margin-bottom:16px;line-height:1.8;white-space:pre-wrap;}
.at{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:10px;}
.at thead tr{border-bottom:1px solid #2A2A26;}.at th{text-align:left;padding:6px 10px;font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#5A5A55;font-family:var(--font-sans);}.at td{padding:5px 10px;border-bottom:1px solid #1E1E1A;vertical-align:top;line-height:1.5;}.at tr:last-child td{border-bottom:none;}.at td:first-child{color:#7A7A72;}.at td:nth-child(2){color:#E8E8E2;font-weight:500;}.at td:nth-child(3){color:#5A5A55;}.at .grp td{padding-top:14px;font-family:var(--font-sans);font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#3A3A36;}
.llm{background:var(--color-page);border:2px solid var(--navy);border-radius:var(--radius-xl);padding:32px 40px;margin-top:64px;}
.llm h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:8px;color:var(--navy);}
.llm h3{font-size:14px;font-weight:600;margin:20px 0 8px;color:var(--color-text);}
.llm h4{font-size:12px;font-weight:600;margin:14px 0 6px;color:var(--color-text-muted);}
.llm p,.llm li{font-size:13px;color:var(--color-text-muted);line-height:1.65;}
.llm ul,.llm ol{padding-left:20px;margin-bottom:12px;}
.llm li{margin-bottom:4px;}
.llm code{font-family:var(--font-mono);font-size:11px;background:var(--color-surface);padding:1px 5px;border-radius:3px;}
.llm table{width:100%;border-collapse:collapse;margin:12px 0;font-size:12px;}
.llm th,.llm td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--color-border);}
.llm th{font-weight:500;color:var(--color-text);font-size:11px;text-transform:uppercase;letter-spacing:.05em;}
.llm td{color:var(--color-text-muted);}
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
</style>
</head>
<body>
<div class="doc">
<div class="doc-header">
<div>
<h1>Annotation-Backed Transcription</h1>
<p>Final spec for the collaborative inline transcription system. Draw turquoise rectangles on the scanned letter &rarr; numbered transcript blocks appear in a side panel &rarr; type what you read. Block-level comment threads with quoted selections for discussion. History in the transcript toolbar. No bottom panel.</p>
</div>
<div class="doc-meta">
Familienarchiv<br/>
<span class="pill pill-g">Final spec</span><br/>
2026-04-04 &middot; @leonievoss
</div>
</div>
<!-- ═══ CORE CONCEPT ═══ -->
<div class="section">
<div class="section-title">Core concept &mdash; Draw-to-Transcribe</div>
<p class="prose">Today, annotations are rectangles on the PDF that open a comment thread in the side panel. By adding a <code>type</code> field to <code>DocumentAnnotation</code>, the same draw-a-rectangle gesture can create a <strong>transcription annotation</strong> (turquoise). A transcription annotation links a PDF region to an editable text block in the right panel.</p>
<p class="prose">Comments live <strong>inside transcript blocks</strong> as block-level threads. Users can select a word or phrase before commenting &mdash; the selection is <strong>auto-quoted</strong> into the comment message (e.g. <code>&gt; &ldquo;Breslau&rdquo;</code>) rather than structurally anchored to character offsets. This avoids fragile offset tracking that breaks when text is edited. The quote is a display hint, not a structural anchor. Yellow comment annotations are <strong>disabled in transcribe mode</strong> &mdash; only turquoise transcription rectangles appear on the PDF.</p>
</div>
<div class="jh jh-b">
<div class="jn">T</div>
<div><h2>Draw-to-transcribe workflow</h2><p>Draw a rectangle around a passage on the scan. A transcript block appears in the editor, linked to that region. Type what you read. Rinse and repeat down the page. Others can join and work on different blocks simultaneously.</p><div class="fl">Reuses: AnnotationLayer + PdfViewer + CommentThread &middot; New: TranscriptBlock + TranscriptEditor + type:transcription</div></div>
</div>
<!-- ═══ WHAT STAYS / CHANGES / NEW ═══ -->
<div class="section">
<div class="section-title">What stays, what changes, what&rsquo;s new</div>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;font-size:12px;line-height:1.6;">
<div style="background:#fff;border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px;">
<div style="font-weight:600;color:var(--navy);margin-bottom:6px;">Reused as-is</div>
<ul style="padding-left:16px;color:var(--color-text-muted);">
<li><code>AnnotationLayer</code> &mdash; draw rects on PDF</li>
<li><code>PdfViewer</code> &mdash; render, zoom, page nav</li>
<li><code>CommentThread</code> &mdash; threaded replies, mentions</li>
<li><code>DocumentAnnotation</code> model &mdash; add <code>type</code> field</li>
<li><code>DocumentComment</code> model &mdash; unchanged</li>
</ul>
</div>
<div style="background:#fff;border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px;">
<div style="font-weight:600;color:var(--orange);margin-bottom:6px;">Repurposed</div>
<ul style="padding-left:16px;color:var(--color-text-muted);">
<li><code>AnnotationSidePanel</code> slot &rarr; becomes the transcript editor panel</li>
<li><code>annotateMode</code> state &rarr; split into <code>annotateMode</code> + <code>transcribeMode</code></li>
<li>Annotation color &rarr; turquoise only in transcribe mode, yellow only in annotate mode (mutually exclusive)</li>
<li><code>AnnotateHintStrip</code> &rarr; new copy for transcribe mode</li>
</ul>
</div>
<div style="background:#fff;border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px;">
<div style="font-weight:600;color:var(--green);margin-bottom:6px;">New</div>
<ul style="padding-left:16px;color:var(--color-text-muted);">
<li><code>transcription_blocks</code> table</li>
<li>Transcript editor component (right panel)</li>
<li>Block-level comment threads (quoted selections)</li>
<li><code>type</code> column on <code>document_annotations</code></li>
<li>History in transcript toolbar</li>
<li>Bottom panel removed (all modes)</li>
</ul>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════════════
SCREEN 1 — DESKTOP TRANSCRIBE MODE
═══════════════════════════════════════════════════════════════════════════ -->
<div class="scr" id="desktop">
<div class="scr-head"><h3>Desktop &mdash; transcribe mode active</h3><span class="scr-id">S1</span></div>
<div class="scr-desc">Two users are collaborating. <strong>Only turquoise</strong> transcription rectangles appear on the PDF &mdash; no yellow comment annotations in transcribe mode. One user (Oma Inge, purple) is editing Block 2. The current user (blue) is editing Block 3. Block 2 has a comment thread where Oma Inge quoted &ldquo;Breslau&rdquo; to discuss the reading. Each block has a &ldquo;Kommentieren&rdquo; button in its footer. The transcript toolbar shows &ldquo;Verlauf&rdquo; (history). No bottom panel.</div>
<div class="previews">
<div class="prev-col">
<div class="bp-lbl">Desktop &middot; 1040px</div>
<div class="desk">
<div class="fa-nav">
<div class="fa-logo">FAMILIENARCHIV</div>
<div class="fa-link">Dokumente</div>
<div class="fa-link">Personen</div>
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
</div>
<div class="fa-topbar">
<div class="back">&larr;</div>
<div class="title">Brief von Heinrich an Martha, 14. Mai 1943</div>
<div style="flex:1"></div>
<div class="presence" style="margin-right:4px;"><div class="presence-dot" style="background:var(--blue);"></div> Du</div>
<div class="presence" style="margin-right:4px;"><div class="presence-dot" style="background:var(--purple);"></div> Oma Inge</div>
<div style="width:1px;height:16px;background:#e4e2d7;margin:0 4px;"></div>
<div class="details-toggle">Details &#9660;</div>
<div style="width:1px;height:16px;background:#e4e2d7;margin:0 4px;"></div>
<div class="fa-topbar-btn transcribe">&#9998; Transkribieren</div>
<div class="fa-topbar-btn ghost">Annotieren</div>
</div>
<!-- Hint strip -->
<div class="hint-strip trans-hint">
<span>Transkribieren</span>
<span class="hint-step">&mdash; Markiere eine Textpassage im Scan, um einen Transkriptions-Block anzulegen</span>
</div>
<div class="split" style="height:400px;">
<!-- PDF with annotation rectangles -->
<div class="split-left">
<div class="pdf-area" style="flex:1;">
<div class="paper" style="width:55%;min-height:240px;position:relative;">
<div style="font-size:7px;color:#8A8070;font-style:italic;margin-bottom:4px;opacity:.7;">Liebe Martha,</div>
<div class="pl" style="width:90%;"></div><div class="ps" style="width:85%;"></div><div class="ps" style="width:92%;"></div>
<div class="pl" style="width:78%;"></div><div class="ps" style="width:88%;"></div><div class="ps" style="width:70%;"></div>
<div class="pl" style="width:84%;"></div><div class="ps" style="width:90%;"></div><div class="ps" style="width:60%;"></div>
<div class="pl" style="width:75%;"></div><div class="ps" style="width:82%;"></div>
<div style="font-size:6px;color:#8A8070;margin-top:6px;text-align:right;opacity:.7;">Dein Heinrich</div>
<!-- Transcription annotations (turquoise) -->
<div class="ann-rect trans" style="left:2%;top:0%;width:50%;height:10%;">
<div class="ann-num">1</div>
</div>
<div class="ann-rect trans active" style="left:2%;top:14%;width:96%;height:32%;">
<div class="ann-num">2</div>
</div>
<div class="ann-rect trans" style="left:2%;top:50%;width:96%;height:22%;">
<div class="ann-num">3</div>
</div>
<div class="ann-rect trans" style="left:20%;top:80%;width:60%;height:12%;">
<div class="ann-num">4</div>
</div>
<!-- No yellow comment annotations in transcribe mode —
only turquoise transcription rects on the PDF -->
</div>
</div>
</div>
<div class="split-handle"></div>
<!-- Transcript editor panel -->
<div class="split-right" style="width:380px;">
<!-- Transcript toolbar -->
<div class="trans-toolbar">
<span style="font-size:7px;font-weight:600;color:var(--navy);">4 Bl&ouml;cke</span>
<div style="flex:1;"></div>
<div class="trans-toolbar-btn">&#9776; Sortieren</div>
<div class="trans-toolbar-btn">&#128337; Verlauf</div>
<span style="font-size:7px;color:var(--green-dark);">&#10003; Gespeichert</span>
</div>
<!-- Block list -->
<div style="flex:1;overflow-y:auto;padding:6px 8px;background:#fff;display:flex;flex-direction:column;gap:4px;">
<!-- Block 1 — Greeting (done) -->
<div class="tblock">
<div class="tblock-head"><div class="num">1</div> Anrede <span style="margin-left:auto;color:var(--green-dark);">&#10003;</span></div>
<div class="tblock-body">Liebe Martha,</div>
</div>
<!-- Block 2 — Main body (edited by Oma Inge) -->
<div class="tblock active">
<div class="tblock-head active-bg">
<div class="num">2</div> Hauptteil
<div class="presence" style="margin-left:auto;"><div class="presence-dot" style="background:var(--purple);width:4px;height:4px;"></div> Oma Inge</div>
</div>
<div class="tblock-body editing hl-purple">ich schreibe Dir heute aus dem Lazarett in Breslau. Mach Dir keine Sorgen, es geht mir den Umst&auml;nden entsprechend gut. Der Arzt sagt <span class="illegible">[unleserlich]</span> Wochen noch dauern wird.</div>
<!-- Block-level thread with quoted selection -->
<div class="inline-thread">
<div class="thread-head">&#128172; 2 Kommentare</div>
<div class="thread-msg">
<div class="thread-av" style="background:var(--purple);">OI</div>
<div>
<strong style="font-size:7px;">Oma Inge</strong> &middot; <span style="font-size:7px;color:var(--color-text-muted);">vor 12 Min.</span>
<div style="border-left:2px solid var(--color-border);padding-left:4px;margin:2px 0;font-size:7px;font-style:italic;color:var(--color-text-muted);">&ldquo;Breslau&rdquo;</div>
<div>Ich bin sicher, das ist &ldquo;Breslau&rdquo; &mdash; Heinrich war dort im Lazarett.</div>
</div>
</div>
<div class="thread-msg">
<div class="thread-av" style="background:var(--blue);">DU</div>
<div>
<strong style="font-size:7px;">Du</strong> &middot; <span style="font-size:7px;color:var(--color-text-muted);">vor 8 Min.</span>
<div>Stimmt, danke! Lass ich so.</div>
</div>
</div>
<div class="thread-reply">
<input placeholder="Antworten..."/>
<div class="resolve-btn">&#10003; L&ouml;sen</div>
</div>
</div>
<!-- Block footer with comment button -->
<div class="tblock-footer">
<span style="cursor:pointer;color:var(--orange);display:flex;align-items:center;gap:2px;">&#128172; Kommentieren</span>
<span style="margin-left:auto;font-size:5px;color:var(--color-text-muted);">Text markieren f&uuml;r Zitat</span>
</div>
</div>
<!-- Block 3 — Family (edited by current user) -->
<div class="tblock active" style="border-color:var(--blue);box-shadow:0 0 0 1px var(--blue);">
<div class="tblock-head" style="background:rgba(45,125,210,.06);">
<div class="num">3</div> Familie
<div class="presence" style="margin-left:auto;"><div class="presence-dot" style="background:var(--blue);width:4px;height:4px;"></div> Du</div>
</div>
<div class="tblock-body editing hl-blue">Die Kinder sollen wissen, dass ich an sie denke. Sag dem kleinen Fritz, er soll auf seine Mutter aufpassen.<span class="trans-cursor"></span></div>
<div class="tblock-footer">
<span style="cursor:pointer;color:var(--color-text-muted);display:flex;align-items:center;gap:2px;">&#128172; Kommentieren</span>
</div>
</div>
<!-- Block 4 — Closing (done) -->
<div class="tblock">
<div class="tblock-head"><div class="num">4</div> Schluss <span style="margin-left:auto;color:var(--green-dark);">&#10003;</span></div>
<div class="tblock-body">In ewiger Liebe,<br/>Dein Heinrich</div>
</div>
<!-- Add block CTA -->
<div class="tblock empty" style="text-align:center;padding:8px;font-size:7px;color:var(--color-text-muted);cursor:pointer;">
Markiere eine weitere Passage im Scan, um Block 5 anzulegen
</div>
</div>
<div class="status-bar">
<span>Block 3 aktiv</span>
<span>Oma Inge &middot; Block 2</span>
<span style="margin-left:auto;">1 offene Diskussion</span>
</div>
</div>
</div>
<!-- NO bottom panel in transcribe mode -->
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════════════
SCREEN 2 — COMMENT FLOW: SELECT → QUOTE → DISCUSS
═══════════════════════════════════════════════════════════════════════════ -->
<div class="scr" id="comment-flow">
<div class="scr-head"><h3>Comment flow &mdash; select, quote, discuss</h3><span class="scr-id">S2</span></div>
<div class="scr-desc">The user has selected &ldquo;[unleserlich]&rdquo; in Block 2 and clicked &ldquo;Kommentieren&rdquo;. The comment input opens with the selection auto-quoted. After posting, the comment appears in the thread with the quote displayed as an indented blockquote. This shows the full lifecycle: selection &rarr; quoted input &rarr; posted comment.</div>
<div class="scr-var"><strong>Block-level threads + quoted selections</strong> &mdash; no char-offset anchoring, no fragile highlights. The quote is frozen text in the message body.</div>
<div class="previews">
<div class="prev-col">
<div class="bp-lbl">Desktop &middot; comment input open with auto-quote</div>
<div class="desk" style="min-height:380px;">
<div class="fa-nav">
<div class="fa-logo">FAMILIENARCHIV</div>
<div class="fa-link">Dokumente</div>
<div class="fa-link">Personen</div>
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
</div>
<div class="fa-topbar">
<div class="back">&larr;</div>
<div class="title">Brief von Heinrich an Martha, 14. Mai 1943</div>
<div style="flex:1"></div>
<div class="details-toggle">Details &#9660;</div>
<div style="width:1px;height:16px;background:#e4e2d7;margin:0 4px;"></div>
<div class="fa-topbar-btn transcribe">&#9998; Transkribieren</div>
<div class="fa-topbar-btn ghost">Annotieren</div>
</div>
<div class="split" style="height:290px;">
<div class="split-left">
<div class="pdf-area" style="flex:1;">
<div class="paper" style="width:55%;min-height:160px;position:relative;">
<div style="font-size:7px;color:#8A8070;font-style:italic;margin-bottom:4px;opacity:.7;">Liebe Martha,</div>
<div class="pl" style="width:90%;"></div><div class="ps" style="width:85%;"></div><div class="ps" style="width:92%;"></div>
<div class="pl" style="width:78%;"></div><div class="ps" style="width:88%;"></div><div class="ps" style="width:70%;"></div>
<div class="ann-rect trans active" style="left:2%;top:14%;width:96%;height:40%;"><div class="ann-num">2</div></div>
</div>
</div>
</div>
<div class="split-handle"></div>
<div class="split-right" style="width:380px;">
<div class="trans-toolbar">
<span style="font-size:7px;font-weight:600;color:var(--navy);">4 Bl&ouml;cke</span>
<div style="flex:1;"></div>
<span style="font-size:7px;color:var(--green-dark);">&#10003; Gespeichert</span>
</div>
<div style="flex:1;overflow-y:auto;padding:6px 8px;background:#fff;display:flex;flex-direction:column;gap:4px;">
<!-- Block 2 with text selection + open comment input -->
<div class="tblock active">
<div class="tblock-head active-bg"><div class="num">2</div> Hauptteil</div>
<div class="tblock-body editing">ich schreibe Dir heute aus dem Lazarett in Breslau. Mach Dir keine Sorgen. Der Arzt sagt <span style="background:rgba(45,125,210,.25);border-radius:1px;padding:0 1px;">[unleserlich]</span> Wochen noch dauern wird.</div>
<!-- Comment input — open, with auto-quoted selection -->
<div style="margin:0 8px 5px;padding:6px 8px;border-radius:4px;border:1px solid var(--orange);background:#fff;">
<div style="font-size:6px;font-weight:600;color:var(--orange-dark);margin-bottom:3px;">Neuer Kommentar zu Block 2</div>
<!-- Auto-quoted selection shown as editable blockquote -->
<div style="border-left:2px solid var(--color-border);padding-left:4px;margin-bottom:3px;font-size:7px;font-style:italic;color:var(--color-text-muted);display:flex;align-items:center;gap:3px;">
&gt; &ldquo;[unleserlich]&rdquo;
<span style="font-size:5px;color:var(--color-text-muted);font-style:normal;cursor:pointer;margin-left:auto;">&#10005; Zitat entfernen</span>
</div>
<div style="display:flex;gap:3px;">
<input style="flex:1;font-size:7px;padding:3px 5px;border:1px solid var(--color-border);border-radius:3px;background:var(--color-page);" value='Könnte "sechs" oder "acht" sein. Wer hat die Originale?'/>
<button style="font-size:6px;font-weight:600;padding:3px 8px;border-radius:3px;background:var(--navy);color:#fff;border:none;cursor:pointer;">Senden</button>
</div>
</div>
<div class="tblock-footer">
<span style="cursor:pointer;color:var(--orange);display:flex;align-items:center;gap:2px;font-weight:600;">&#128172; Kommentieren</span>
<span style="margin-left:auto;font-size:5px;color:var(--color-text-muted);">Text markieren f&uuml;r Zitat</span>
</div>
</div>
<!-- Block 3 — with an existing posted comment showing the quote -->
<div class="tblock">
<div class="tblock-head"><div class="num">3</div> Familie</div>
<div class="tblock-body">Die Kinder sollen wissen, dass ich an sie denke.</div>
<!-- Posted comment thread with quoted selection -->
<div class="inline-thread">
<div class="thread-head">&#128172; 1 Kommentar</div>
<div class="thread-msg">
<div class="thread-av" style="background:var(--purple);">OI</div>
<div>
<strong style="font-size:7px;">Oma Inge</strong> &middot; <span style="font-size:7px;color:var(--color-text-muted);">vor 5 Min.</span>
<div style="border-left:2px solid var(--color-border);padding-left:4px;margin:2px 0;font-size:7px;font-style:italic;color:var(--color-text-muted);">&ldquo;Die Kinder&rdquo;</div>
<div>Fritz und Lotte. Fritz war damals 4, Lotte 7.</div>
</div>
</div>
<div class="thread-reply">
<input placeholder="Antworten..."/>
</div>
</div>
<div class="tblock-footer">
<span style="cursor:pointer;color:var(--color-text-muted);display:flex;align-items:center;gap:2px;">&#128172; Kommentieren</span>
</div>
</div>
</div>
<div class="status-bar"><span>Block 2 aktiv</span><span style="margin-left:auto;">2 Kommentare</span></div>
</div>
</div>
</div>
</div>
</div>
<div class="agent">
<h4>Comment flow &middot; Select → Quote → Discuss</h4>
<pre>/* Comment flow for block-level threads with quoted selections:
*
* 1. TRIGGER: user clicks "Kommentieren" in block footer.
* Alternatively: Ctrl+Shift+K when block is focused.
*
* 2. AUTO-QUOTE: if text is selected in the block body (via mouse or keyboard),
* the selection is captured and pre-filled as a blockquote in the comment input:
* > "[unleserlich]"
* The user can edit or remove the quote before sending (× button).
* If no text was selected → input opens empty (general block comment).
*
* 3. STORAGE: the quote is stored as part of the comment `content` field.
* Markdown blockquote syntax: "> \"Breslau\"\nI think this is Breslau."
* The block_id FK on DocumentComment links the comment to its block.
* NO char_offset_start/end columns. The quote is just text.
*
* 4. DISPLAY: the quote renders as an indented italic line with a left border,
* above the comment text. It's visually distinct but structurally just content.
*
* 5. RESILIENCE: if the transcription text changes after quoting, nothing breaks.
* The quote is a frozen snapshot. The discussion context is preserved.
* Compare to char-offset anchoring where an edit would shift all offsets
* and potentially point to the wrong text.
*
* 6. THREAD: replies to a quoted comment don't need their own quotes —
* the parent comment provides context. Standard CommentThread reply flow.
*
* 7. MOBILE: "Kommentieren" button always visible in footer.
* Selecting text → auto-quote works the same via touch selection.
* Thread collapsed to "N Kommentare" row, tap to expand. */</pre>
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
<tr class="grp"><td colspan="3">Comment input</td></tr>
<tr><td>Container</td><td>border:orange, bg:white, radius:4px, mx:8px</td><td>Appears below block body, above footer</td></tr>
<tr><td>Quote display</td><td>left-border:2px line, italic, 7px muted</td><td>Editable — user can modify or remove</td></tr>
<tr><td>Remove quote</td><td>"× Zitat entfernen" link, 5px, top-right of quote</td><td>Converts to general block comment</td></tr>
<tr><td>Input field</td><td>flex:1, 7px, border:line, bg:page, radius:3px</td><td>Auto-focuses when opened</td></tr>
<tr><td>Send button</td><td>"Senden", 6px/600, navy bg, white text</td><td>Enter to send, Shift+Enter for newline</td></tr>
<tr class="grp"><td colspan="3">Posted comment with quote</td></tr>
<tr><td>Quote in thread</td><td>left-border:2px line, italic, 7px muted</td><td>Read-only — frozen snapshot of selected text</td></tr>
<tr><td>Message below</td><td>8px normal text, below the quote</td><td>Standard CommentThread message styling</td></tr>
<tr class="grp"><td colspan="3">Data model</td></tr>
<tr><td>block_id</td><td>UUID FK → transcription_blocks (nullable)</td><td>Links comment to its block</td></tr>
<tr><td>content</td><td>TEXT with markdown blockquote</td><td>&gt; "quoted text"\nComment message</td></tr>
<tr><td>No char offsets</td><td></td><td>Intentional. See spec rationale.</td></tr>
</tbody></table>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════════════
SCREEN 3 — HISTORY IN TRANSCRIPT TOOLBAR
═══════════════════════════════════════════════════════════════════════════ -->
<div class="scr" id="history">
<div class="scr-head"><h3>Desktop &mdash; history panel open</h3><span class="scr-id">S3</span></div>
<div class="scr-desc">Clicking &ldquo;Verlauf&rdquo; in the transcript toolbar opens a collapsible history panel between the toolbar and the block list. Shows recent changes with word-level diffs, just like the existing <code>PanelHistory</code> component but embedded in the transcript panel instead of the bottom panel.</div>
<div class="previews">
<div class="prev-col">
<div class="bp-lbl">Desktop &middot; 1040px &middot; history open</div>
<div class="desk" style="min-height:540px;">
<div class="fa-nav">
<div class="fa-logo">FAMILIENARCHIV</div>
<div class="fa-link">Dokumente</div>
<div class="fa-link">Personen</div>
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
</div>
<div class="fa-topbar">
<div class="back">&larr;</div>
<div class="title">Brief von Heinrich an Martha, 14. Mai 1943</div>
<div style="flex:1"></div>
<div class="details-toggle">Details &#9660;</div>
<div style="width:1px;height:16px;background:#e4e2d7;margin:0 4px;"></div>
<div class="fa-topbar-btn transcribe">&#9998; Transkribieren</div>
<div class="fa-topbar-btn ghost">Annotieren</div>
</div>
<div class="hint-strip trans-hint">
<span>Transkribieren</span>
<span class="hint-step">&mdash; Markiere eine Textpassage im Scan</span>
</div>
<div class="split" style="height:420px;">
<div class="split-left">
<div class="pdf-area" style="flex:1;">
<div class="paper" style="width:55%;min-height:220px;position:relative;">
<div style="font-size:7px;color:#8A8070;font-style:italic;margin-bottom:4px;opacity:.7;">Liebe Martha,</div>
<div class="pl" style="width:90%;"></div><div class="ps" style="width:85%;"></div><div class="ps" style="width:92%;"></div>
<div class="pl" style="width:78%;"></div><div class="ps" style="width:88%;"></div><div class="ps" style="width:70%;"></div>
<div class="pl" style="width:84%;"></div><div class="ps" style="width:90%;"></div>
<div class="ann-rect trans" style="left:2%;top:0%;width:50%;height:10%;"><div class="ann-num">1</div></div>
<div class="ann-rect trans" style="left:2%;top:14%;width:96%;height:32%;"><div class="ann-num">2</div></div>
<div class="ann-rect trans" style="left:2%;top:50%;width:96%;height:22%;"><div class="ann-num">3</div></div>
<div class="ann-rect trans" style="left:20%;top:80%;width:60%;height:12%;"><div class="ann-num">4</div></div>
</div>
</div>
</div>
<div class="split-handle"></div>
<div class="split-right" style="width:380px;">
<!-- Toolbar with history active -->
<div class="trans-toolbar">
<span style="font-size:7px;font-weight:600;color:var(--navy);">4 Bl&ouml;cke</span>
<div style="flex:1;"></div>
<div class="trans-toolbar-btn">&#9776; Sortieren</div>
<div class="trans-toolbar-btn active">&#128337; Verlauf</div>
<span style="font-size:7px;color:var(--green-dark);">&#10003; Gespeichert</span>
</div>
<!-- History panel (collapsible) -->
<div class="history-panel">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;">
<span style="font-size:6px;font-weight:600;color:var(--navy);text-transform:uppercase;letter-spacing:.06em;">Letzte &Auml;nderungen</span>
<span style="font-size:6px;color:var(--color-text-muted);cursor:pointer;">Alle anzeigen &rarr;</span>
</div>
<div class="history-entry">
<span class="he-date">14:23</span>
<span class="he-user">Oma Inge</span>
<span class="he-diff">Block 2: ...Lazarett in <span class="he-add">Breslau</span><span class="he-del">Bresla</span>...</span>
</div>
<div class="history-entry">
<span class="he-date">14:18</span>
<span class="he-user">Du</span>
<span class="he-diff">Block 3: <span class="he-add">Die Kinder sollen wissen, dass ich an sie denke.</span></span>
</div>
<div class="history-entry">
<span class="he-date">14:12</span>
<span class="he-user">Oma Inge</span>
<span class="he-diff">Block 2: <span class="he-add">ich schreibe Dir heute aus dem Lazarett</span></span>
</div>
<div class="history-entry">
<span class="he-date">14:05</span>
<span class="he-user">Du</span>
<span class="he-diff">Block 1: <span class="he-add">Liebe Martha,</span></span>
</div>
</div>
<!-- Blocks below history -->
<div style="flex:1;overflow-y:auto;padding:6px 8px;background:#fff;display:flex;flex-direction:column;gap:4px;">
<div class="tblock">
<div class="tblock-head"><div class="num">1</div> Anrede <span style="margin-left:auto;color:var(--green-dark);">&#10003;</span></div>
<div class="tblock-body">Liebe Martha,</div>
</div>
<div class="tblock">
<div class="tblock-head"><div class="num">2</div> Hauptteil</div>
<div class="tblock-body">ich schreibe Dir heute aus dem Lazarett in Breslau...</div>
</div>
<div class="tblock active" style="border-color:var(--blue);box-shadow:0 0 0 1px var(--blue);">
<div class="tblock-head" style="background:rgba(45,125,210,.06);"><div class="num">3</div> Familie</div>
<div class="tblock-body editing hl-blue">Die Kinder sollen wissen...<span class="trans-cursor"></span></div>
</div>
<div class="tblock">
<div class="tblock-head"><div class="num">4</div> Schluss <span style="margin-left:auto;color:var(--green-dark);">&#10003;</span></div>
<div class="tblock-body">In ewiger Liebe,<br/>Dein Heinrich</div>
</div>
</div>
<div class="status-bar"><span>Block 3 aktiv</span><span style="margin-left:auto;">&#10003; Gespeichert</span></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════════════════════
SCREEN 3 — MOBILE TRANSCRIBE MODE
═══════════════════════════════════════════════════════════════════════════ -->
<div class="scr" id="mobile">
<div class="scr-head"><h3>Mobile &mdash; transcribe mode</h3><span class="scr-id">S4</span></div>
<div class="scr-desc">On mobile, the PDF collapses to a 90px strip at the top. Annotation rectangles are visible as thin outlines. Transcript blocks stack vertically below. The history button is in the toolbar above the blocks. Inline threads expand in-place.</div>
<div class="previews">
<div class="prev-col">
<div class="bp-lbl">Mobile &middot; 320px</div>
<div class="phone" style="height:600px;">
<div class="pst"><b>14:23</b><span>&bull;&bull;&bull; WiFi &#128267;</span></div>
<div class="pb">
<div style="background:#fff;border-bottom:1px solid #e4e2d7;padding:6px 12px;display:flex;align-items:center;gap:6px;">
<span style="font-size:11px;color:var(--color-text-muted);">&larr;</span>
<span style="font-family:Georgia,serif;font-size:11px;color:var(--navy);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">Brief von Heinrich, 14.05.1943</span>
<span style="font-size:7px;font-weight:700;padding:2px 6px;border-radius:3px;background:var(--turquoise);color:var(--navy);">Transkr.</span>
</div>
<!-- PDF strip with annotations -->
<div style="background:#D4D0C8;height:90px;display:flex;align-items:center;justify-content:center;position:relative;border-bottom:2px solid var(--turquoise);">
<div style="background:#FFFEF8;width:45%;padding:6px 8px;box-shadow:0 1px 4px rgba(0,0,0,.12);border-radius:1px;position:relative;">
<div style="font-size:5px;color:#8A8070;font-style:italic;opacity:.7;">Liebe Martha,</div>
<div style="height:2px;background:#C4BDB0;opacity:.4;margin:2px 0;width:80%;"></div>
<div style="height:1.5px;background:#C4BDB0;opacity:.25;margin:1px 0;width:90%;"></div>
<div style="height:1.5px;background:#C4BDB0;opacity:.25;margin:1px 0;width:70%;"></div>
<div style="position:absolute;left:2%;top:0;width:50%;height:18%;border:1px solid var(--turquoise);border-radius:1px;opacity:.5;"></div>
<div style="position:absolute;left:2%;top:22%;width:96%;height:35%;border:1px solid var(--turquoise);border-radius:1px;background:rgba(0,199,177,.1);"></div>
</div>
</div>
<!-- Toolbar -->
<div style="background:#fff;border-bottom:1px solid #e4e2d7;display:flex;align-items:center;padding:4px 12px;gap:4px;">
<span style="font-size:8px;font-weight:600;color:var(--navy);">4 Bl&ouml;cke</span>
<div style="flex:1;"></div>
<div style="font-size:7px;font-weight:600;padding:3px 6px;border-radius:3px;border:1px solid var(--color-border);color:var(--color-text-muted);">&#128337; Verlauf</div>
<span style="font-size:7px;color:var(--green-dark);">&#10003;</span>
</div>
<!-- Block list -->
<div style="flex:1;overflow-y:auto;padding:8px 12px;background:#fff;">
<div style="border:1px solid var(--color-border);border-radius:5px;overflow:hidden;margin-bottom:6px;">
<div style="padding:3px 8px;font-size:6px;font-weight:600;color:var(--color-text-muted);display:flex;align-items:center;gap:3px;background:var(--sand);"><div style="width:12px;height:12px;border-radius:50%;background:var(--navy);color:#fff;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:700;">1</div> Anrede <span style="margin-left:auto;color:var(--green-dark);">&#10003;</span></div>
<div style="padding:4px 8px;font-family:Georgia,serif;font-size:10px;line-height:1.6;">Liebe Martha,</div>
</div>
<div style="border:1px solid var(--turquoise);border-radius:5px;overflow:hidden;margin-bottom:6px;box-shadow:0 0 0 1px var(--turquoise);">
<div style="padding:3px 8px;font-size:6px;font-weight:600;color:var(--color-text-muted);display:flex;align-items:center;gap:3px;background:rgba(0,199,177,.08);"><div style="width:12px;height:12px;border-radius:50%;background:var(--navy);color:#fff;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:700;">2</div> Hauptteil <span style="font-size:5px;color:var(--purple);margin-left:auto;">Oma Inge</span></div>
<div style="padding:4px 8px;font-family:Georgia,serif;font-size:10px;line-height:1.6;border-left:2px solid var(--purple);">ich schreibe Dir heute aus dem Lazarett in Breslau...</div>
<!-- Inline thread (collapsed on mobile — tap to expand) -->
<div style="padding:3px 8px;border-top:1px solid var(--color-subtle);display:flex;align-items:center;gap:3px;">
<span style="font-size:7px;color:var(--orange);">&#128172;</span>
<span style="font-size:7px;color:var(--color-text-muted);">1 Diskussion &middot; &ldquo;Breslau&rdquo;</span>
<span style="font-size:7px;color:var(--color-text-muted);margin-left:auto;">&#9660;</span>
</div>
</div>
<div style="border:1px solid var(--blue);border-radius:5px;overflow:hidden;margin-bottom:6px;box-shadow:0 0 0 1px var(--blue);">
<div style="padding:3px 8px;font-size:6px;font-weight:600;color:var(--color-text-muted);display:flex;align-items:center;gap:3px;background:rgba(45,125,210,.06);"><div style="width:12px;height:12px;border-radius:50%;background:var(--navy);color:#fff;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:700;">3</div> Familie <span style="font-size:5px;color:var(--blue);margin-left:auto;">Du</span></div>
<div style="padding:4px 8px;font-family:Georgia,serif;font-size:10px;line-height:1.6;border-left:2px solid var(--blue);">Die Kinder sollen wissen...<span class="trans-cursor"></span></div>
</div>
<div style="border:1px solid var(--color-border);border-radius:5px;overflow:hidden;margin-bottom:6px;">
<div style="padding:3px 8px;font-size:6px;font-weight:600;color:var(--color-text-muted);display:flex;align-items:center;gap:3px;background:var(--sand);"><div style="width:12px;height:12px;border-radius:50%;background:var(--navy);color:#fff;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:700;">4</div> Schluss <span style="margin-left:auto;color:var(--green-dark);">&#10003;</span></div>
<div style="padding:4px 8px;font-family:Georgia,serif;font-size:10px;line-height:1.6;">In ewiger Liebe,<br/>Dein Heinrich</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ═══ AGENT TABLES ═══ -->
<div class="agent">
<h4>Annotation-backed transcription &middot; Core implementation spec</h4>
<pre>/* Core flow: enter transcribe mode → crosshair cursor on PDF → draw rect → creates:
* 1. DocumentAnnotation(type:"transcription", turquoise) in the DB
* 2. TranscriptionBlock(annotation_id, text:"", sort_order:N) in the DB
* 3. Editable block in the right panel, linked to the annotation
* Clicking an annotation rect on PDF scrolls to + highlights the matching block.
* Clicking a block header highlights the matching rect on PDF.
*
* COMMENTS: block-level threads with quoted selections.
* - Each block has a "Kommentieren" button in its footer.
* - If text is selected when clicking "Kommentieren", the selection is auto-quoted
* into the comment (> "Breslau"). The quote is plain text in the message body,
* NOT a structural char-offset anchor. It doesn't break when text changes.
* - Threads are anchored to block_id only (no char offsets).
* - Yellow comment annotations are DISABLED in transcribe mode.
* Only turquoise transcription rects on the PDF. One annotation type per mode.
*
* History: "Verlauf" button in transcript toolbar toggles a collapsible panel
* showing recent changes with word-level diffs per block.
* Auto-save: debounced PATCH to /api/transcription-blocks/{blockId} (500ms).
* Bottom panel: removed entirely (all modes). Metadata → topbar drawer. */</pre>
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
<tr class="grp"><td colspan="3">Annotation reuse</td></tr>
<tr><td>Draw gesture</td><td>Existing AnnotationLayer.onDraw(rect)</td><td>Same pointer events. crosshair cursor.</td></tr>
<tr><td>Annotation color</td><td>turquoise (#00C7B1) for transcription</td><td>Yellow annotations disabled in transcribe mode</td></tr>
<tr><td>Annotation type</td><td>New column: type VARCHAR "transcription"|"comment"</td><td>Default "comment" for backward compat</td></tr>
<tr><td>Number badge</td><td>16px navy circle, top-left of rect</td><td>Sort order number, matches block number</td></tr>
<tr class="grp"><td colspan="3">Transcript blocks (right panel)</td></tr>
<tr><td>Block card</td><td>border:1px line, radius:5px, active: turquoise glow</td><td>Header: number + label + presence. Body: contenteditable.</td></tr>
<tr><td>Block label</td><td>Editable text, defaults: Anrede, Hauptteil, Schluss</td><td>Double-click to rename</td></tr>
<tr><td>Empty state</td><td>Dashed border, "noch leer" italic text</td><td>Focus to start typing</td></tr>
<tr><td>Add block CTA</td><td>Dashed card: "Markiere eine Passage im Scan..."</td><td>Not clickable — directs user to draw on PDF</td></tr>
<tr class="grp"><td colspan="3">Block-level comment threads</td></tr>
<tr><td>Trigger</td><td>"Kommentieren" button in block footer</td><td>Always visible — no hover-reveal</td></tr>
<tr><td>Quoted selection</td><td>If text selected → auto-quoted into comment body</td><td>Plain text quote (&gt; "Breslau"), NOT char-offset anchor</td></tr>
<tr><td>Quote display</td><td>Left border + italic, above the comment text</td><td>Decorative only — doesn't link to text range</td></tr>
<tr><td>Thread UI</td><td>orange left-border, orange-tint bg, below block body</td><td>Block-level anchor (block_id). Reuses CommentThread.</td></tr>
<tr><td>Footer hint</td><td>"Text markieren für Zitat" in 5px muted text</td><td>Only shown when block is active/focused</td></tr>
<tr><td>Resolve</td><td>"✓ Lösen" button collapses thread</td><td>Resolved threads hidden by default, toggle to show</td></tr>
<tr><td>Mobile</td><td>Threads collapsed to "2 Kommentare" row, tap to expand</td><td>Saves vertical space on small screens</td></tr>
<tr class="grp"><td colspan="3">Yellow annotations in transcribe mode</td></tr>
<tr><td>Status</td><td>Disabled — draw gesture only creates turquoise rects</td><td>Existing yellow annotations still visible (read-only)</td></tr>
<tr><td>Annotate mode</td><td>Still available via topbar "Annotieren" button</td><td>Exits transcribe mode, enters annotate mode (yellow)</td></tr>
<tr class="grp"><td colspan="3">History (transcript toolbar)</td></tr>
<tr><td>Toggle</td><td>"🕗 Verlauf" button in transcript toolbar</td><td>Active state: navy bg, white text</td></tr>
<tr><td>Panel</td><td>Collapsible, between toolbar and block list</td><td>bg:color-page, border:line, radius:5px</td></tr>
<tr><td>Entries</td><td>Time + user + block ref + word-level diff</td><td>Reuses diffWords from 'diff' library</td></tr>
<tr><td>"Alle anzeigen"</td><td>Link to full history view (reuses PanelHistory)</td><td>Opens in a modal or replaces block list temporarily</td></tr>
<tr class="grp"><td colspan="3">Interaction</td></tr>
<tr><td>Click rect → block</td><td>scrollIntoView + active state on block</td><td>Turquoise glow on both rect and block</td></tr>
<tr><td>Click block → rect</td><td>PDF scrolls/zooms to show the annotation</td><td>If multi-page: switches page</td></tr>
<tr><td>Delete block</td><td>Deletes annotation + block + threads</td><td>Confirm dialog if threads exist</td></tr>
<tr><td>Reorder blocks</td><td>Drag handle in block header</td><td>Updates sort_order via PATCH</td></tr>
<tr class="grp"><td colspan="3">Presence (collaborative)</td></tr>
<tr><td>Dots in topbar</td><td>Colored dot + user name, flex row</td><td>Max 3 shown, "+N" overflow</td></tr>
<tr><td>Block-level presence</td><td>Colored dot + name in block header</td><td>Left border color matches user</td></tr>
<tr><td>Implementation</td><td>WebSocket presence via Y.js (future)</td><td>MVP: polling-based, 5s interval</td></tr>
<tr class="grp"><td colspan="3">Auto-save</td></tr>
<tr><td>Debounce</td><td>500ms after last keystroke</td><td>PATCH /api/transcription-blocks/{blockId}</td></tr>
<tr><td>Status</td><td>"✓ Gespeichert" in toolbar, fades after 3s</td><td>"Speichern..." while request in-flight</td></tr>
<tr><td>Conflict</td><td>Last-write-wins for MVP</td><td>Y.js CRDT for future collaborative editing</td></tr>
</tbody></table>
</div>
<!-- ═══ LLM IMPLEMENTATION GUIDE ═══ -->
<div class="llm">
<h2>Implementation Guide &mdash; Annotation-Backed Transcription</h2>
<h3>1. Data Model Changes</h3>
<h4>Flyway migration: <code>document_annotations</code></h4>
<ul>
<li>Add <code>type VARCHAR(20) NOT NULL DEFAULT 'comment'</code>.</li>
<li>Values: <code>'comment'</code> (existing behavior) or <code>'transcription'</code>.</li>
<li>Backward compatible &mdash; all existing annotations default to <code>'comment'</code>.</li>
</ul>
<h4>New table: <code>transcription_blocks</code></h4>
<table>
<thead><tr><th>Column</th><th>Type</th><th>Notes</th></tr></thead>
<tbody>
<tr><td><code>id</code></td><td>UUID PK</td><td>Generated</td></tr>
<tr><td><code>annotation_id</code></td><td>UUID FK → document_annotations</td><td>Links block to its PDF rectangle</td></tr>
<tr><td><code>document_id</code></td><td>UUID FK → documents</td><td>Denormalized for efficient queries</td></tr>
<tr><td><code>text</code></td><td>TEXT</td><td>The transcription content</td></tr>
<tr><td><code>label</code></td><td>VARCHAR(100)</td><td>"Anrede", "Hauptteil", etc.</td></tr>
<tr><td><code>sort_order</code></td><td>INT</td><td>Display order in the editor</td></tr>
<tr><td><code>created_by</code></td><td>UUID FK → app_users</td><td></td></tr>
<tr><td><code>updated_by</code></td><td>UUID FK → app_users</td><td></td></tr>
<tr><td><code>created_at</code></td><td>TIMESTAMP</td><td>@CreationTimestamp</td></tr>
<tr><td><code>updated_at</code></td><td>TIMESTAMP</td><td>@UpdateTimestamp</td></tr>
</tbody>
</table>
<h4>Block-level comments: <code>document_comments</code></h4>
<ul>
<li>Add <code>block_id UUID FK → transcription_blocks</code> (nullable).</li>
<li><strong>No char_offset columns.</strong> Quoted selections are stored as plain text in the comment <code>content</code> field using blockquote markdown syntax (<code>&gt; &ldquo;Breslau&rdquo;</code>). This is intentional &mdash; char offsets break when text is edited and require OT/CRDT to maintain. Quotes are a display hint, not a structural anchor.</li>
<li>Backward compatible &mdash; <code>block_id</code> is nullable, existing comments unaffected.</li>
</ul>
<h4>Backward compatibility: <code>Document.transcription</code></h4>
<p>The existing <code>transcription</code> TEXT field becomes a <strong>computed read-only view</strong>: <code>SELECT string_agg(text, E'\n\n' ORDER BY sort_order) FROM transcription_blocks WHERE document_id = ?</code>. Write operations go through the block API. This keeps search indexing, export, and the read-only <code>PanelTranscription</code> working without changes.</p>
<h3>2. Annotation Color Convention &amp; Mode Exclusivity</h3>
<table>
<thead><tr><th>Type</th><th>Color</th><th>Hex</th><th>On click</th><th>When active</th></tr></thead>
<tbody>
<tr><td>Comment</td><td>Yellow</td><td><code>#FFC800</code></td><td>Opens AnnotationSidePanel (existing)</td><td>Annotate mode only</td></tr>
<tr><td>Transcription</td><td>Turquoise</td><td><code>#00C7B1</code></td><td>Highlights matching block in transcript editor</td><td>Transcribe mode only</td></tr>
</tbody>
</table>
<p><strong>Mode exclusivity:</strong> In transcribe mode, only turquoise rects can be drawn. Existing yellow comment annotations from annotate mode are still <em>visible</em> on the PDF (read-only, dimmed) but cannot be created or interacted with. The &ldquo;Annotieren&rdquo; button exits transcribe mode and enters annotate mode (and vice versa). This prevents overlapping annotation types and avoids user confusion about which comment system to use.</p>
<h3>3. Component Architecture</h3>
<table>
<thead><tr><th>Component</th><th>Change</th></tr></thead>
<tbody>
<tr><td><code>AnnotationLayer.svelte</code></td><td>Pass <code>type</code> to <code>onDraw</code> callback. Render turquoise vs yellow based on annotation type. Add number badges for transcription annotations.</td></tr>
<tr><td><code>PdfViewer.svelte</code></td><td>Split <code>handleAnnotationDraw</code> into two paths (annotate vs transcribe). Route <code>handleAnnotationClick</code> to either side panel or transcript editor.</td></tr>
<tr><td><code>AnnotationSidePanel.svelte</code></td><td>No change &mdash; still handles comment-type annotations in annotate mode. Hidden in transcribe mode.</td></tr>
<tr><td><code>TranscriptEditor.svelte</code> (new)</td><td>Right panel. Renders transcript toolbar + block list. Manages block CRUD, auto-save, block-level comment threads.</td></tr>
<tr><td><code>TranscriptBlock.svelte</code> (new)</td><td>Single block card. contenteditable body, header with number/label/presence, footer with &ldquo;Kommentieren&rdquo; button, thread slot below body.</td></tr>
<tr><td><code>BlockCommentThread.svelte</code> (new)</td><td>Comment thread anchored to a block. Shows quoted selections as blockquotes. Reuses <code>CommentThread</code> internally for replies/mentions.</td></tr>
<tr><td><code>TranscriptToolbar.svelte</code> (new)</td><td>Block count, sort button, history toggle, save status.</td></tr>
<tr><td><code>TranscriptHistory.svelte</code> (new)</td><td>Collapsible panel. Reuses <code>diffWords</code> from the <code>diff</code> library. Shows recent changes per block.</td></tr>
<tr><td><code>DocumentBottomPanel.svelte</code></td><td>Removed entirely. Metadata lives in the topbar drawer (see companion spec). Discussion, transcription, and history are all inline.</td></tr>
<tr><td><code>documents/[id]/+page.svelte</code></td><td>Add <code>transcribeMode</code> state. Conditionally render TranscriptEditor vs bottom panel.</td></tr>
</tbody>
</table>
<h3>4. API Endpoints</h3>
<table>
<thead><tr><th>Method</th><th>Path</th><th>Notes</th></tr></thead>
<tbody>
<tr><td>POST</td><td><code>/api/documents/{id}/annotations</code></td><td>Existing, but now accepts <code>type</code> field. If <code>type="transcription"</code>, also creates a TranscriptionBlock.</td></tr>
<tr><td>GET</td><td><code>/api/documents/{id}/transcription-blocks</code></td><td>Returns all blocks ordered by sort_order.</td></tr>
<tr><td>PATCH</td><td><code>/api/transcription-blocks/{blockId}</code></td><td>Update text, label, or sort_order. Auto-save target.</td></tr>
<tr><td>DELETE</td><td><code>/api/transcription-blocks/{blockId}</code></td><td>Deletes block + its annotation + any anchored comments.</td></tr>
<tr><td>PATCH</td><td><code>/api/transcription-blocks/reorder</code></td><td>Bulk update sort_order for drag-and-drop reordering.</td></tr>
</tbody>
</table>
<h3>5. Draw-to-Transcribe Workflow</h3>
<ol>
<li>User enters <strong>Transcribe mode</strong> (topbar button, turquoise). Hint strip appears. Yellow comment annotations become read-only/dimmed. Only turquoise rects can be drawn.</li>
<li>Crosshair cursor on PDF (same as annotate mode). User draws a rectangle around a handwriting passage.</li>
<li><code>AnnotationLayer.onDraw(rect)</code> fires. <code>PdfViewer</code> calls <code>POST /api/documents/{id}/annotations</code> with <code>type: "transcription"</code>.</li>
<li>Backend creates <code>DocumentAnnotation</code> + <code>TranscriptionBlock</code> (empty text, next sort_order).</li>
<li>Frontend receives the created annotation + block. The transcript editor scrolls to the new empty block and focuses it.</li>
<li>User types the transcription. Auto-save debounces to <code>PATCH /api/transcription-blocks/{blockId}</code>.</li>
<li>Repeat: draw next rectangle, type next block.</li>
</ol>
<h3>6. Comment Flow &mdash; Block-Level Threads with Quoted Selections</h3>
<p>Comments are anchored to <strong>blocks</strong>, not character offsets. This is a deliberate simplification:</p>
<h4>Why not char-offset anchoring?</h4>
<ul>
<li>When someone edits the transcription text, all character offsets downstream shift.</li>
<li>Keeping offsets in sync requires operational transforms (OT) or CRDT &mdash; that&rsquo;s the Y.js future work, not MVP.</li>
<li>A stale offset pointing to the wrong word is worse than a quoted snippet that no longer matches but still shows what was discussed.</li>
</ul>
<h4>How it works</h4>
<ol>
<li>User clicks <strong>&ldquo;Kommentieren&rdquo;</strong> in a block footer.</li>
<li>If text is selected in the block body, the selection is <strong>auto-quoted</strong> into the comment input: <code>&gt; &ldquo;Breslau&rdquo;</code>. The user can edit or remove the quote before sending.</li>
<li>If no text is selected, the comment input opens empty &mdash; a general block-level comment.</li>
<li>The comment is saved as a <code>DocumentComment</code> with <code>block_id</code> set. The quoted text is part of the <code>content</code> field (markdown blockquote syntax).</li>
<li>The thread renders below the block body with an orange left-border. Quoted text appears as an indented italic blockquote above the comment message.</li>
<li>Replies work the same as existing <code>CommentThread</code> &mdash; no changes needed.</li>
</ol>
<h4>What happens when text changes after quoting?</h4>
<p>Nothing breaks. The quote is a frozen snapshot of what the user selected. If &ldquo;Bresla&rdquo; was later corrected to &ldquo;Breslau&rdquo;, the original quote still reads <code>&gt; &ldquo;Bresla&rdquo;</code> with Oma Inge&rsquo;s comment &ldquo;I think this is Breslau.&rdquo; The context is preserved. No orphaned anchors, no broken highlights.</p>
<h4>Footer hint</h4>
<p>When a block is focused/active, the footer shows a subtle hint: <em>&ldquo;Text markieren f&uuml;r Zitat&rdquo;</em> (select text for a quote). This teaches the quoted-selection pattern without requiring documentation.</p>
<h3>7. History in Transcript Toolbar</h3>
<ul>
<li>The &ldquo;Verlauf&rdquo; button in the toolbar toggles <code>TranscriptHistory.svelte</code>.</li>
<li>The panel renders between the toolbar and the block list (pushes blocks down).</li>
<li>It shows recent changes per block, using <code>diffWords</code> from the <code>diff</code> library (same as existing <code>PanelHistory</code>).</li>
<li>Each entry: timestamp, user name, block reference (e.g. &ldquo;Block 2&rdquo;), and a word-level diff snippet.</li>
<li>&ldquo;Alle anzeigen&rdquo; opens a full history view &mdash; can reuse the existing <code>PanelHistory</code> component in a modal.</li>
<li>Data source: the existing document version history API, filtered/grouped by block.</li>
</ul>
<h3>8. Accessibility</h3>
<ul>
<li>Transcription blocks: <code>role="region"</code> with <code>aria-label="Transkriptions-Block N: [label]"</code></li>
<li>Block body: <code>contenteditable</code> with <code>aria-multiline="true"</code></li>
<li>Number badges on PDF: <code>aria-label="Transkriptions-Bereich N"</code></li>
<li>Comment button: <code>aria-label="Block N kommentieren"</code></li>
<li>History toggle: <code>aria-expanded</code>, <code>aria-controls="transcript-history"</code></li>
<li>Focus order: topbar &rarr; hint strip &rarr; PDF (for drawing) &rarr; transcript blocks (in sort order) &rarr; comment button &rarr; status bar</li>
<li>Keyboard: Tab between blocks, Enter to edit, Escape to deselect. Ctrl+Shift+N to prompt draw on PDF. Ctrl+Shift+K to open comment on focused block.</li>
</ul>
<h3>9. Companion Spec</h3>
<p>The expandable metadata header (labeled &ldquo;Details &#9660;&rdquo; toggle) is specified separately in <code>expandable-metadata-header-spec.html</code>. Together, these two specs fully eliminate the bottom panel in <strong>all modes</strong>: metadata &rarr; header drawer, transcription &rarr; inline split view, discussion &rarr; inline threads, history &rarr; transcript toolbar. One consistent pattern &mdash; no mode-dependent UI structure.</p>
</div>
</div>
</body>
</html>