feat: auto-open transcription panel when navigating from mission-control cards #376
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
First-time users click document links from the "Text markieren" or "Text transkribieren" mission-control columns and then get lost on the document page. The transcription/segmentation panel is not visible by default — the PDF preview dominates, and the panel trigger button is overlooked.
Observed failure mode: user clicks card → document page loads → user cannot find where to act → drops off.
Goal
When a user arrives at a document page from a mission-control card, the transcription panel should already be open. No additional interaction required.
Requirements
REQ-ONBOARD-001 — Deep-link into transcription panel from mission control
Acceptance Criteria
Given a user clicks a card link from the "Text markieren" column,
when the document page loads,
then the transcription panel is open without any additional user interaction.
Given a user clicks a card link from the "Text transkribieren" column,
when the document page loads,
then the transcription panel is open without any additional user interaction.
Given a user navigates to a document URL without the flag (e.g. from search, person detail, or direct URL),
when the page loads,
then the panel remains closed by default — existing behaviour is unchanged.
Implementation Hint
Both columns use the same panel. A single query parameter (e.g.
?task=transcribe) on the cardhrefis sufficient. The document page reads the param on mount and opens the panel if present.Both
SegmentationColumn.svelteandTranscriptionColumn.sveltegenerate links as/documents/{id}— these need the flag appended.Out of Scope
Column heading copy improvements (label clarity) are tracked separately as REQ-ONBOARD-002 and are not part of this issue.
👨💻 Felix Brandt — Senior Fullstack Developer
Observations
SegmentationColumn.svelte:39, one inTranscriptionColumn.svelte:44) plus ataskparam check inonMountinside+page.svelte.transcribeModeis already a$state(false)on line 40 of+page.svelte. Setting it totruefromonMountwhen the param is present requires no structural change to the component.scrollToCommentFromQueryutility indeepLinkScroll.tshandlescommentId/annotationIddeep links. Thetaskparam is a different concern — don't extend that utility. Handle it as a separate, sequential check inonMountbefore thescrollToCommentFromQuerycall. Keep the responsibilities separated.SegmentationColumn.svelte.spec.tstest on line 65 explicitly assertshref="/documents/abc-123"and will break when the href changes to include?task=transcribe. That test must be updated as part of this PR.TranscriptionColumn.svelte.spec.tsexists — a parallel spec file should be created with the same coverage plus the new link assertion.Recommendations
SegmentationColumnlink test to assert?task=transcribe, watch it fail; (2) update the href; (3) add aTranscriptionColumnspec with the same assertion; (4) write a test for theonMountbehavior — when the URL contains?task=transcribe,transcribeModeshould betrueafter mount.onStripUrlpattern already used inscrollToCommentFromQueryat line 381 of+page.svelte) so a browser refresh doesn't re-trigger the panel open. UsereplaceState(page.url.pathname, ...).const TASK_PARAM = 'task'andconst TASK_TRANSCRIBE = 'transcribe'if this pattern is likely to grow. If it's genuinely one-off, inline is fine — KISS wins.Open Decisions
commentIdpattern. Leaving it means bookmarked mission-control links always auto-open the panel (which could be useful for re-visits). What's the intended behavior?🏛️ Markus Keller — Application Architect
Observations
taskquery param is ephemeral client state — it signals intent at page load, not persisted data. The right home isonMountin+page.svelte, read viapage.url.searchParams, not server-side in+page.server.ts. SvelteKit'sloadfunction should not be involved.scrollToCommentFromQueryutility has a single, clear responsibility: navigate to a comment annotation from a notification click. Addingtask=transcribehandling there would give it two responsibilities and pollute a utility that other flows may reuse. The parallelonMountcheck is the correct structural decision.Recommendations
onMount, not in a derived or effect. The param is consumed once on mount; it is not reactive state that needs tracking.scrollToCommentFromQuerycall so that if both params are present (unlikely but possible via a manually constructed URL),transcribeModeis alreadytruewhenscrollToCommentFromQueryruns and tries to set it.Open Decisions
None. The architectural approach is straightforward and fits the existing patterns.
🔒 Nora "NullX" Steiner — Security Engineer
Observations
taskparam is read client-side inonMountonly — it does not reach the backend, does not affect authorization, does not change what data is fetched.?task=transcribeon a document they can already access gets… the transcription panel open. This is identical to clicking the panel button themselves. Zero privilege escalation.+page.server.tsand Spring Security before any client-side code runs. The param does not affect that gate.=== 'transcribe'), never rendered into the DOM.Recommendations
aria-label="task: {taskParam}"). The comparison should beparam === 'transcribe'and nothing else. This is the natural implementation but worth stating explicitly.replaceState). This is primarily a UX concern (no accidental bookmark of an auto-open URL) but also avoids the param lingering in referrer headers on any subsequent navigation.Open Decisions
None — this is a clean, low-risk UI-state change with no security implications.
🧪 Sara Holt — QA Engineer
Observations
SegmentationColumn.svelte.spec.tsline 65 assertstoHaveAttribute('href', '/documents/abc-123'). After the href changes to/documents/abc-123?task=transcribe, this test fails. This is a required update, not optional cleanup.TranscriptionColumn.svelte.spec.tsexists. The transcription column currently has zero component-level test coverage. This PR is the right time to create it.onMountparam-reading behavior in+page.svelteis not currently tested at any layer. The component is complex enough that adding an isolated test for this specific behavior may require mockingpage.url, which is$app/state— doable withvi.mock.Test Plan
Unit layer (Vitest component tests):
SegmentationColumn.svelte.spec.ts— update line 65: asserthrefends with?task=transcribeTranscriptionColumn.svelte.spec.ts— new file, matching structure, assert?task=transcribeon linksdeepLinkScroll.spec.ts— no changes needed (task param is handled separately)E2E layer (Playwright) — optional but recommended:
4. Navigate to
/documents/{id}?task=transcribe, assert the transcription panel is visible without user interaction5. Navigate to
/documents/{id}(no param), assert panel is not visibleOpen Decisions
🎨 Leonie Voss — UX Designer & Accessibility Strategist
Observations
transcribeModeflips totrueprogrammatically on mount, the panel opens but focus stays at the top of the page. A keyboard/screen reader user arrives at the document, hears the title, and does not know a panel has opened below. The panel opening should either (a) move focus into the panel header or first interactive element, or (b) be announced via anaria-live="polite"region.scrollToCommentFromQueryhandles this: it callsel.scrollIntoView()andel.focus()after the tick. The task-param path should do the same — scroll the panel into view and move focus to the panel's first heading or close button.replaceState(page.url.pathname, page.state ?? {})to clean the URL. The existing deep-link pattern (line 381) already does this. A URL with?task=transcribein the address bar would confuse users who copy-paste links to share documents, since their recipients would also land with an open panel.Recommendations
transcribeMode = trueinonMount, callawait tick()and then scroll the panel header into view and move focus there — matching the pattern used for comment deep-links.aria-expandedto reflect open/closed state — this is already a gap independent of this change.Open Decisions
None — concrete recommendations above cover the gaps.
🚀 Tobias Wendt — DevOps & Platform Engineer
Observations
npm run check(svelte-check) andnpm run test(Vitest) are the relevant gates. The existingSegmentationColumntest will fail until updated — this is expected and CI should catch it before merge.Recommendations
?task=transcribeURLs are ever generated server-side (e.g., in notification emails), they'll go through the same SvelteKit SSR path without issue — the param is read inonMount(client-side), so SSR renders the closed-panel state and hydration opens it. No hydration mismatch risk.Open Decisions
None.
🗳️ Decision Queue — 2 decisions needed before implementation
URL Behaviour
?task=transcribefrom the URL after the panel opens, or keep it?commentIddeep-link pattern (onStripUrlat+page.svelte:381), prevents the param appearing in shared links or copy-pasted URLs, avoids surprising recipients who'd land with a panel already open.Test Coverage
onMountall cooperate correctly.(Raised by: Felix, Nora, Leonie for URL behaviour; Sara for test coverage)
🎯 Discussion Resolutions
After reviewing the persona feedback with the user, here are the agreed decisions:
Theme 1 — Implementation location
taskparam check lives inonMountof+page.svelte, separate fromscrollToCommentFromQuery. Runs before thescrollToCommentFromQuerycall.scrollToCommentFromQueryhas a single responsibility (comment notification deep-linking). Thetaskparam is a different concern and must not be mixed in.+page.server.tsis not involved — this is client-only UI state that does not affect data fetching.Theme 2 — href changes
?task=transcribeto links in both column components.SegmentationColumn.svelte:39→href="/documents/{doc.id}?task=transcribe"TranscriptionColumn.svelte:44→href="/documents/{doc.id}?task=transcribe"Theme 3 — URL stripping
replaceState(page.url.pathname, page.state ?? {}).onStripUrlpattern used byscrollToCommentFromQuery. Prevents the param appearing in shared/copy-pasted URLs and avoids surprising recipients.transcribeMode = true→await tick()→ scroll + focus →replaceState.Theme 4 — Focus management
await tick(), scroll the panel into view and move focus to the panel's close button.transcribeModeflips programmatically, focus stays at the top of the page — keyboard and screen reader users get no signal that the panel opened. The close button is the safest focus target (always present, clearly labelled, immediate escape). Mirrorsel.scrollIntoView()+el.focus()pattern fromscrollToCommentFromQuery.Theme 5 — Test coverage
SegmentationColumn.svelte.spec.tsline 65: href assertion must include?task=transcribeTranscriptionColumn.svelte.spec.ts: new file mirroring SegmentationColumn spec structure, including link assertiondeepLinkScroll.spec.ts: no changes (task param handled separately)These resolutions are the authoritative design for implementation. The
implementskill will read this comment alongside the original issue.✅ Implementation complete
Three commits on
feat/issue-376-auto-open-transcription-panel:690eb234— feat(SegmentationColumn): deep-link to transcription panel via ?task=transcribehreffrom/documents/{id}to/documents/{id}?task=transcribeSegmentationColumn.svelte.spec.tslink assertion to expect the param6f40b2c3— feat(TranscriptionColumn): deep-link to transcription panel via ?task=transcribehreffrom/documents/{id}to/documents/{id}?task=transcribeTranscriptionColumn.svelte.spec.tslink assertion to expect the param84ba728e— feat(document-page): auto-open transcription panel when ?task=transcribe is presentonMountof+page.svelte, readstaskparam beforescrollToCommentFromQuerytask === 'transcribe': setstranscribeMode = true, waits a tick, scrolls the close button ([data-testid="panel-close"]) into view, moves focus to it, then strips the param viareplaceStateAll 10 component tests pass. Type check clean on affected files.