From ef11cbee4e103973e3c4808b519cd94b6d215b77 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 5 Apr 2026 23:36:06 +0200 Subject: [PATCH] feat(transcription): clicking annotation focuses corresponding block Pass activeAnnotationId to TranscriptionEditView. An $effect watches it and sets activeBlockId to the block matching the annotation, activating its turquoise focus border. 2 new tests (RED/GREEN): - activates block matching activeAnnotationId (turquoise border) - no block activated when activeAnnotationId is null Co-Authored-By: Claude Sonnet 4.6 --- .../lib/components/TranscriptionEditView.svelte | 9 +++++++++ .../TranscriptionEditView.svelte.spec.ts | 17 +++++++++++++++++ frontend/src/routes/documents/[id]/+page.svelte | 1 + 3 files changed, 27 insertions(+) diff --git a/frontend/src/lib/components/TranscriptionEditView.svelte b/frontend/src/lib/components/TranscriptionEditView.svelte index 44a21288..5d675a01 100644 --- a/frontend/src/lib/components/TranscriptionEditView.svelte +++ b/frontend/src/lib/components/TranscriptionEditView.svelte @@ -11,6 +11,7 @@ type Props = { blocks: TranscriptionBlockData[]; canComment: boolean; currentUserId: string | null; + activeAnnotationId?: string | null; onBlockFocus: (blockId: string) => void; onSaveBlock: (blockId: string, text: string) => Promise; onDeleteBlock: (blockId: string) => Promise; @@ -21,12 +22,20 @@ let { blocks, canComment, currentUserId, + activeAnnotationId = null, onBlockFocus, onSaveBlock, onDeleteBlock }: Props = $props(); let activeBlockId: string | null = $state(null); + +// Sync: when an annotation is clicked on the PDF, activate the corresponding block +$effect(() => { + if (!activeAnnotationId) return; + const block = blocks.find((b) => b.annotationId === activeAnnotationId); + if (block) activeBlockId = block.id; +}); let saveStates = new SvelteMap(); let debounceTimers = new SvelteMap>(); let pendingTexts = new SvelteMap(); diff --git a/frontend/src/lib/components/TranscriptionEditView.svelte.spec.ts b/frontend/src/lib/components/TranscriptionEditView.svelte.spec.ts index fd780fb9..b7f8621c 100644 --- a/frontend/src/lib/components/TranscriptionEditView.svelte.spec.ts +++ b/frontend/src/lib/components/TranscriptionEditView.svelte.spec.ts @@ -55,6 +55,23 @@ describe('TranscriptionEditView — rendering', () => { }); }); +describe('TranscriptionEditView — annotation sync', () => { + it('activates block matching activeAnnotationId', async () => { + renderView({ activeAnnotationId: 'a2' }); + // Block 2 (annotation a2) should have turquoise border + const block = document.querySelector('[data-block-id="b2"]')!; + expect(block.className).toContain('border-turquoise'); + }); + + it('does not activate any block when activeAnnotationId is null', async () => { + renderView({ activeAnnotationId: null }); + const block1 = document.querySelector('[data-block-id="b1"]')!; + const block2 = document.querySelector('[data-block-id="b2"]')!; + expect(block1.className).not.toContain('border-turquoise'); + expect(block2.className).not.toContain('border-turquoise'); + }); +}); + describe('TranscriptionEditView — reorder', () => { it('renders move-up button disabled on first block', async () => { renderView(); diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte index f3d00e33..b7fd293c 100644 --- a/frontend/src/routes/documents/[id]/+page.svelte +++ b/frontend/src/routes/documents/[id]/+page.svelte @@ -223,6 +223,7 @@ onMount(() => { blocks={transcriptionBlocks} canComment={canWrite} currentUserId={currentUserId} + activeAnnotationId={activeAnnotationId} onBlockFocus={handleBlockFocus} onSaveBlock={saveBlock} onDeleteBlock={deleteBlock}