fix(document-detail): force edit panel on notification deep-link
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m39s
CI / OCR Service Tests (push) Successful in 30s
CI / Backend Unit Tests (push) Failing after 2m46s

Comments only render inside TranscriptionEditView, so a deep-link
into a document with existing reviewed transcriptions landed the
user in read mode with no comment element in the DOM — the scroll
target silently missed.

scrollToCommentFromQuery now takes a setPanelMode callback and calls
it with 'edit' whenever both query params are present. The page's
own transcribe-mode $effect checks a skipInitialPanelMode flag the
deep-link flow sets, so its default-panel-mode logic doesn't race
against the explicit override.

Two new helper tests pin the contract: panel mode is forced to
'edit' both when transcribe mode is off (entering fresh) and when
it is already on (same-page notification click).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-21 15:22:38 +02:00
parent 3bf0b38c42
commit b07f9efa9c
3 changed files with 42 additions and 1 deletions

View File

@@ -18,6 +18,7 @@ function buildOpts(overrides: Overrides = {}): DeepLinkScrollOptions {
return {
transcribeMode: true,
setTranscribeMode: vi.fn(),
setPanelMode: vi.fn(),
loadBlocks: vi.fn().mockResolvedValue(undefined),
setActiveAnnotationId: vi.fn(),
flashAnnotation: vi.fn(),
@@ -126,4 +127,26 @@ describe('scrollToCommentFromQuery', () => {
expect(opts.onStripUrl).toHaveBeenCalled();
});
it('forces panel mode to "edit" so the comment DOM exists on reviewed documents', async () => {
const url = new URL(
`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
);
const opts = buildOpts();
await scrollToCommentFromQuery(url, opts);
expect(opts.setPanelMode).toHaveBeenCalledWith('edit');
});
it('forces panel mode to "edit" even when transcribe mode is already on', async () => {
const url = new URL(
`https://app/documents/doc-1?commentId=${COMMENT_ID}&annotationId=${ANNOTATION_ID}`
);
const opts = buildOpts({ transcribeMode: true });
await scrollToCommentFromQuery(url, opts);
expect(opts.setPanelMode).toHaveBeenCalledWith('edit');
});
});

View File

@@ -1,6 +1,7 @@
export type DeepLinkScrollOptions = {
transcribeMode: boolean;
setTranscribeMode: (value: boolean) => void;
setPanelMode: (mode: 'read' | 'edit') => void;
loadBlocks: () => Promise<void>;
setActiveAnnotationId: (id: string) => void;
flashAnnotation: (annotationId: string) => void;
@@ -25,6 +26,11 @@ export async function scrollToCommentFromQuery(
await opts.loadBlocks();
}
// Comments only render in edit mode — force it so the deep-link target
// exists in the DOM even if the document already has reviewed transcriptions
// (which default the panel to read mode).
opts.setPanelMode('edit');
opts.setActiveAnnotationId(annotationId);
await opts.afterTick();

View File

@@ -40,6 +40,10 @@ let activeAnnotationId = $state<string | null>(null);
let highlightBlockId = $state<string | null>(null);
let flashAnnotationId = $state<string | null>(null);
let pdfStripExpanded = $state(false);
// Flag set by the deep-link helper so the transcribe-mode $effect does not
// overwrite the panelMode it picked (e.g. forcing 'edit' on notification
// click-through). One-shot: consumed after the effect's loadBlocks resolves.
let skipInitialPanelMode = $state(false);
const prefersReducedMotion = $derived(
typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches
@@ -281,7 +285,11 @@ async function checkOcrStatus() {
$effect(() => {
if (transcribeMode) {
loadTranscriptionBlocks().then(() => {
panelMode = transcriptionBlocks.length > 0 ? 'read' : 'edit';
if (skipInitialPanelMode) {
skipInitialPanelMode = false;
} else {
panelMode = transcriptionBlocks.length > 0 ? 'read' : 'edit';
}
});
checkOcrStatus();
}
@@ -309,6 +317,10 @@ onMount(() => {
scrollToCommentFromQuery(new URL(page.url), {
transcribeMode,
setTranscribeMode: (v) => (transcribeMode = v),
setPanelMode: (m) => {
skipInitialPanelMode = true;
panelMode = m;
},
loadBlocks: loadTranscriptionBlocks,
setActiveAnnotationId: (id) => (activeAnnotationId = id),
flashAnnotation: (annotationId) => {