Files
AI-Prototype/prompts/ocr-viewer-redesign2.md
2026-05-13 10:03:38 -04:00

11 KiB

P44 OCR Viewer — Pass 2 Diff View + Edit Mode Reference Pane

Scope: p44-ocr-viewer.js, p44-ocr-viewer.css, p44-ocr-viewer.html. Do not touch PDF rendering, zoom, Pass 1 flag logic, or the corrections storage model.

BACKGROUND — how corrections are stored

Each page's corrections live in state.corrections[pageNum] with this shape: javascript

{ original: "raw OCR text as loaded", pass1: "volunteer-corrected text", pass2: "senior editor text" }

The diff always compares original against pass1. Never use state.currentPage inside diff functions — always pass pageNum explicitly to avoid page-bleed bugs.

PART 1 — HTML (p44-ocr-viewer.html)

Inside #text-panel-body, replace: html

With: html

In the right panel header, add a diff toggle button immediately before #btn-edit: html

⇄ Diff

Remove

entirely — it is replaced by the new diff system.

PART 2 — CSS (p44-ocr-viewer.css)

Add at the end of the file: css

/* ── Diff toolbar ─────────────────────────────────────────────── */ #diff-toolbar { display: none; align-items: center; gap: 10px; padding: 5px 16px; background: var(--surface); border-bottom: 1px solid var(--border); font-size: 10px; color: var(--text-dim); flex-shrink: 0; } #diff-toolbar .diff-legend { display: flex; align-items: center; gap: 10px; flex: 1; } #diff-toolbar .diff-count { font-size: 10px; color: var(--gold-dim); letter-spacing: 0.04em; } .diff-swatch { display: inline-flex; align-items: center; gap: 5px; font-size: 10px; color: var(--text-dim); } .diff-swatch-del { width: 24px; height: 10px; background: rgba(208,80,80,0.22); border-radius: 2px; } .diff-swatch-ins { width: 24px; height: 10px; background: rgba(92,184,92,0.18); border-radius: 2px; }

/* ── Diff inline highlights ───────────────────────────────────── */ .diff-del { background: rgba(208,80,80,0.22); color: #e07070; border-radius: 2px; padding: 0 2px; text-decoration: line-through; font-size: 11px; } .diff-ins { background: rgba(92,184,92,0.18); color: #7ed87e; border-radius: 2px; padding: 0 2px; }

/* ── Editor wrap — vertical split ─────────────────────────────── */ #editor-wrap { display: none; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; border-top: 1px solid var(--gold-dim); }

/* ── Diff reference pane (top half of editor split) ───────────── */ #diff-reference { display: none; flex: 1; min-height: 0; overflow-y: auto; border-bottom: 1px solid var(--border); padding: 12px 20px; font-size: 12.5px; line-height: 1.75; color: var(--text); background: var(--surface); white-space: pre-wrap; font-family: 'Georgia', 'Times New Roman', serif; } #diff-reference-label { font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--gold-dim); margin-bottom: 8px; padding-bottom: 5px; border-bottom: 1px solid var(--border); }

/* ── Editor bottom half ───────────────────────────────────────── */ #editor-bottom { display: flex; flex: 1; min-height: 0; overflow: hidden; }

Also update the existing #editor-wrap rule — remove flex-direction and border-top from it since those are now set above. The existing rule in the file should become: css

#editor-wrap { flex: 1; min-height: 0; overflow: hidden; }

The new rule added above replaces it fully — delete the old one to avoid conflict.

PART 3 — JS (p44-ocr-viewer.js)

3a — New state flag

Inside the state object, add: javascript

diffMode: false,

3b — DOM refs

Add after the existing editor DOM refs: javascript

const btnDiff = document.getElementById('btn-diff'); const diffToolbar = document.getElementById('diff-toolbar'); const diffReference = document.getElementById('diff-reference'); const editorBottom = document.getElementById('editor-bottom');

3c — Word-level diff engine

Add this new function. Place it directly above buildChangeSummary(): javascript

function buildInlineDiff(originalText, pass1Text) { const orig = (originalText || '').split(/(\s+)/); const edit = (pass1Text || '').split(/(\s+)/); const result = [];

let i = 0, j = 0; while (i < orig.length || j < edit.length) { if (i >= orig.length) { result.push({ type: 'ins', text: edit[j++] }); } else if (j >= edit.length) { result.push({ type: 'del', text: orig[i++] }); } else if (orig[i] === edit[j]) { result.push({ type: 'same', text: orig[i] }); i++; j++; } else { result.push({ type: 'del', text: orig[i++] }); result.push({ type: 'ins', text: edit[j++] }); } } return result; }

3d — Diff renderer

Add this new function directly below buildInlineDiff(): javascript

function renderInlineDiff(pageNum) { const corr = state.corrections && state.corrections[pageNum]; const originalText = (corr && corr.original) || state.ocrPages[pageNum] || ''; const pass1Text = corr && corr.pass1;

if (!pass1Text) return null;

const tokens = buildInlineDiff(originalText, pass1Text); const delCount = tokens.filter(t => t.type === 'del' && t.text.trim()).length; const insCount = tokens.filter(t => t.type === 'ins' && t.text.trim()).length; const total = Math.max(delCount, insCount);

const html = tokens.map(t => { if (t.type === 'same') return escH(t.text); if (t.type === 'del') return '' + escH(t.text) + ''; if (t.type === 'ins') return '' + escH(t.text) + ''; return ''; }).join('');

return { html, total }; }

3e — Diff toolbar renderer

Add this new function: javascript

function showDiffToolbar(pageNum) { const result = renderInlineDiff(pageNum); if (!result) { diffToolbar.style.display = 'none'; return; } diffToolbar.innerHTML = '

' + ' removed' + ' added' + '
' + '' + result.total + ' change' + (result.total === 1 ? '' : 's') + ' from Pass 1'; diffToolbar.style.display = 'flex'; }

3f — Apply diff to display panel

Add this new function: javascript

function applyDiffToDisplay(pageNum) { if (!state.diffMode) return; const result = renderInlineDiff(pageNum); if (!result) return; textOutput.innerHTML = '

' +
result.html + '
'; }

3g — Apply diff to reference pane (edit mode)

Add this new function: javascript

function applyDiffToReference(pageNum) { const result = renderInlineDiff(pageNum); if (!result || !state.diffMode) { diffReference.style.display = 'none'; return; } diffReference.innerHTML = '

Pass 1 corrections — reference
' + '
' +
result.html + '
'; diffReference.style.display = 'block'; }

3h — Toggle diff mode

Add this new function: javascript

function toggleDiff(pageNum) { const corr = state.corrections && state.corrections[pageNum]; const hasPass1 = corr && corr.pass1;

if (!hasPass1) { setStatus('No Pass 1 corrections on this page — nothing to diff.'); return; }

state.diffMode = !state.diffMode; btnDiff.classList.toggle('active', state.diffMode);

if (state.editMode) { applyDiffToReference(pageNum); } else { if (state.diffMode) { applyDiffToDisplay(pageNum); showDiffToolbar(pageNum); } else { showOCRPage(pageNum); } } }

Add the event listener immediately after the function: javascript

btnDiff.addEventListener('click', () => toggleDiff(state.currentPage));

3i — Update showOCRPage()

At the very end of showOCRPage(), after updateCorrectionIndicator(pageNum) and before the Pass 2 auto-edit block, add: javascript

// Show diff toolbar if page has Pass 1 corrections const corrForPage = state.corrections && state.corrections[pageNum]; const hasPass1 = corrForPage && corrForPage.pass1; if (hasPass1) { showDiffToolbar(pageNum); } else { diffToolbar.style.display = 'none'; state.diffMode = false; btnDiff.classList.remove('active'); }

// Auto-enable diff in Pass 2 if Pass 1 corrections exist if (currentRole === 'pass2' && hasPass1 && !state.editMode) { state.diffMode = true; btnDiff.classList.add('active'); applyDiffToDisplay(pageNum); }

Replace the existing Pass 2 auto-edit block at the bottom of showOCRPage(): javascript

if (currentRole === 'pass2') { if (!state.editMode) enterEditMode(); updatePass2NavButtons(); }

With: javascript

if (currentRole === 'pass2') { updatePass2NavButtons(); }

Note: Pass 2 no longer auto-enters edit mode on page load. The editor opens edit mode manually via the Edit button. This is intentional — the senior editor reviews the diff first, then decides to edit.

3j — Update enterEditMode()

At the end of enterEditMode(), after textEditor.focus(), add: javascript

applyDiffToReference(pageNum);

3k — Update exitEditMode()

At the start of exitEditMode(), before any existing logic, add: javascript

diffReference.style.display = 'none';

After the existing line textOutput.style.display = 'block';, add: javascript

if (state.diffMode) applyDiffToDisplay(pageNum);

3l — Update goToPage()

At the start of goToPage(), before if (state.editMode) exitEditMode();, add: javascript

state.diffMode = false; btnDiff.classList.remove('active'); diffToolbar.style.display = 'none'; diffReference.style.display = 'none';

This ensures diff state is always reset on navigation — prevents any possibility of page-bleed.

VERIFICATION CHECKLIST:

Pass 2 lands on a page with Pass 1 corrections: diff toolbar appears automatically, ⇄ Diff button is active, inline highlights visible
Pass 2 lands on a page with no Pass 1 corrections: diff toolbar hidden, ⇄ Diff button inactive, clicking it shows status message
Clicking ⇄ Diff toggles highlights on/off without changing page or losing position
Clicking Edit while diff is on: reference pane appears above textarea showing highlighted diff, editor opens below
Clicking Edit while diff is off: reference pane hidden, editor fills full height
Toggling diff while in edit mode: reference pane appears/disappears above the open textarea
Navigating to any new page: diff mode resets to off, no highlights carry over from previous page
Pass 1 role: ⇄ Diff button present but only activates on pages with Pass 1 corrections