Commit Graph

115 Commits

Author SHA1 Message Date
Marcel
19e2f65a21 fix(csrf): send X-XSRF-TOKEN on all client-side mutating fetch calls
Some checks failed
CI / fail2ban Regex (pull_request) Has been cancelled
CI / Semgrep Security Scan (pull_request) Has been cancelled
CI / Compose Bucket Idempotency (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / fail2ban Regex (push) Has been cancelled
CI / Semgrep Security Scan (push) Has been cancelled
CI / Compose Bucket Idempotency (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 3m34s
CI / OCR Service Tests (pull_request) Successful in 20s
hooks.server.ts already forwards the CSRF token for server-side fetch
(form actions, load). Client-side XHR calls bypassed it, causing Spring
Security to return 403 before PermissionAspect even ran.

Adds getCsrfToken/withCsrf/makeCsrfFetch to cookies.ts.
useTranscriptionBlocks wraps its injectable fetchImpl with makeCsrfFetch
(covers all block mutations and saveBlockWithConflictRetry).
useBlockAutoSave, TranscriptionEditView, BulkDocumentEditLayout,
OcrTrainingCard, and SegmentationTrainingCard apply withCsrf inline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 20:35:51 +02:00
Marcel
0c0a4830cd ux(transcription): bump dismiss button icon from red-500 to red-600
All checks were successful
nightly / deploy-staging (push) Successful in 4m32s
CI / Unit & Component Tests (pull_request) Successful in 3m19s
CI / OCR Service Tests (pull_request) Successful in 21s
CI / Backend Unit Tests (pull_request) Successful in 3m27s
CI / fail2ban Regex (pull_request) Successful in 41s
CI / Semgrep Security Scan (pull_request) Successful in 20s
CI / Compose Bucket Idempotency (pull_request) Successful in 58s
CI / Unit & Component Tests (push) Successful in 3m30s
CI / OCR Service Tests (push) Successful in 19s
CI / Backend Unit Tests (push) Successful in 3m20s
CI / fail2ban Regex (push) Successful in 41s
CI / Semgrep Security Scan (push) Successful in 18s
CI / Compose Bucket Idempotency (push) Successful in 58s
text-red-500 on bg-red-50 gives ~3.8:1 contrast (passes AA for UI
components at 3:1 but leaves no margin). text-red-600 gives ~5.0:1,
comfortably above the AA threshold with no visual downgrade.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:32:47 +02:00
Marcel
dd843d76c2 a11y(transcription): remove redundant aria-live="polite" from alert div
role="alert" already implies aria-live="assertive". The polite override
caused screen readers to wait for the current announcement to finish
before reading the error — too gentle for a failure state the user just
triggered. Dropping the attribute restores the implicit assertive
behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:31:57 +02:00
Marcel
9601974db0 ux(transcription): bump error banner font size to text-sm for readability
text-xs (12px) is at the lower bound for the 60+ transcriber cohort.
text-sm (14px) matches the visual weight of the progress counter label
above and is more comfortable to read under stress (failed operation).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:30:54 +02:00
Marcel
1782526c99 test(transcription): gate second click on button re-enabled to fix race
Adds an await for the button to become non-disabled between the two
dispatchEvent calls in 'clears error on next successful call'. This
ensures the first async rejection has fully settled and Svelte has
flushed markingAllReviewed before the second click fires.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:29:31 +02:00
Marcel
76ef54e064 test(transcription): cover non-JSON fallback in markAllReviewed error path
Adds a test for when the server returns a non-JSON body (e.g. an nginx
502 HTML page). Confirms the res.json().catch(() => ({})) fallback
produces 'INTERNAL_ERROR' as the thrown message and leaves blocks intact.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:28:39 +02:00
Marcel
f1d1ac3f1a test(transcription): assert error banner shows domain-specific message
Adds toHaveTextContent(m.transcription_mark_all_reviewed_error()) to the
error-present test. The previous check only asserted presence via
role="alert", which would not have caught the dead key bug — the banner
was showing the generic fallback rather than the operation-specific copy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:27:29 +02:00
Marcel
0f48ffede5 fix(transcription): use domain-specific message in markAllReviewed catch
Removes the getErrorMessage() indirection and calls
m.transcription_mark_all_reviewed_error() directly in the catch block.
The previous implementation routed through getErrorMessage(code) which
mapped any error code to the generic m.error_internal_error() fallback,
leaving the domain-specific key unreachable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 21:23:59 +02:00
Marcel
3e72157ee1 test(transcription): update markAllReviewed non-OK test to expect throw
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m14s
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m22s
CI / fail2ban Regex (pull_request) Successful in 41s
CI / Semgrep Security Scan (pull_request) Successful in 18s
CI / Compose Bucket Idempotency (pull_request) Successful in 58s
The function now throws instead of silently returning on failure.
Update the test name and assertion to match the new behaviour, and
verify blocks remain unchanged after the error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:43:21 +02:00
Marcel
e2d3975524 test(transcription): replace hardcoded regex with m.* calls in mark-all spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:40:28 +02:00
Marcel
59e99f862a fix(i18n): wire TranscriptionEditView mark-all button through Paraglide
Replace hardcoded German strings with m.transcription_mark_all_reviewed()
and m.transcription_mark_all_reviewed_disabled().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:39:39 +02:00
Marcel
6b53cbfc5b feat(transcription): show dismissible error banner when markAllReviewed fails
Adds markAllError state and catch block to handleMarkAllReviewed.
Error banner renders below the review progress bar with role="alert"
and aria-live="polite" for screen reader announcement. Dismiss button
clears the error; next successful call also clears it automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:38:28 +02:00
Marcel
e3e8373526 fix(transcription): throw error from markAllReviewed() on non-2xx response
Previously the function silently returned on failure, leaving no way
for callers to detect or surface the error to the user.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:37:21 +02:00
Marcel
f27e2d33a5 test(transcription): add failing tests for markAllReviewed error display
RED phase: 4 new Vitest browser tests that fail because the error
banner and catch block don't exist yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:35:56 +02:00
Marcel
6832300a4b test(viewer): replace hardcoded German strings in PdfControls spec with m.* calls
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m30s
CI / OCR Service Tests (pull_request) Successful in 21s
CI / Backend Unit Tests (pull_request) Successful in 3m18s
CI / fail2ban Regex (pull_request) Successful in 40s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 59s
CI / Unit & Component Tests (push) Successful in 3m30s
CI / OCR Service Tests (push) Successful in 20s
CI / Backend Unit Tests (push) Successful in 3m14s
CI / fail2ban Regex (push) Successful in 42s
CI / Semgrep Security Scan (push) Successful in 19s
CI / Compose Bucket Idempotency (push) Successful in 59s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 17:26:17 +02:00
Marcel
4979ae1867 fix(i18n): wire TranscriptionEditView training label through Paraglide
Replaces hardcoded visible text 'Für Training vormerken' with
m.transcribe_mark_for_training() so the label translates in EN and ES.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 16:53:16 +02:00
Marcel
f458c11a0d fix(i18n): wire PdfControls aria-labels through Paraglide messages
Replaces hardcoded Zurück/Weiter/Verkleinern/Vergrößern aria-label strings
with m.viewer_previous_page(), m.viewer_next_page(), m.viewer_zoom_out(),
and m.viewer_zoom_in() so viewer controls translate in EN and ES locales.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 16:50:58 +02:00
Marcel
9030a7d031 test(confirm-service): normalise vi.mock spelling and remove duplicate-id mocks
Five test files mocked $lib/shared/services/confirm.svelte under BOTH
spellings (.svelte and .svelte.js) within the same file; two more mocked
only the .svelte.js form. Both resolve to the same module URL but register
two distinct Playwright route handlers in @vitest/browser-playwright. The
cleanup logic only removes one, leaving an orphan that fires when the next
session loads the module — crashing the run with
"[birpc] rpc is closed, cannot call resolveManualMock".

This is the exact trigger fixed upstream by vitest PR #10267 (issue #9957).
Normalise every confirm.svelte mock to the no-extension form, matching
production imports and the source file basename (confirm.svelte.ts).

After this commit: 8 confirm.svelte mocks across 8 spec files, all under
one canonical ID. A meta-test (next commit) prevents the duplicate-id
pattern from reappearing.

Refs: #553 · vitest-dev/vitest#9957 · vitest-dev/vitest#10267

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:03:43 +02:00
Marcel
9f1b8b4215 fix(enrichment-block): migrate $app/stores → $app/state to eliminate birpc race
The async vi.mock factory in EnrichmentBlock.svelte.spec.ts performed an
`await import(...)` in its body — the same mechanism #535/#546 fixed for
pdfjs-dist. Issue #553: when Chromium's playwright route handler fetches
the mocked module after the worker's birpc channel has closed, the
factory's RPC roundtrip raises `[birpc] rpc is closed, cannot call
"resolveManualMock"` and the run exits 1.

Migrate EnrichmentBlock from the deprecated `$app/stores.navigating`
(store) to the modern `$app/state.navigating` (reactive proxy). The
spec uses vi.hoisted + a sync vi.mock factory with a getter that defers
the read — no dynamic import in the factory body. Delete the now-unused
__mocks__/navigatingStore.ts.

Fix path applied: $app/state migration (Markus's recommendation /
Felix's Path 2). See ADR-012.

Refs #553

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:00:44 +02:00
Marcel
d21ba8fed2 refactor(pdf-viewer-tests): extract shared fake, add loadDocument error path, fix assertions
Some checks failed
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / OCR Service Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / fail2ban Regex (pull_request) Has been cancelled
CI / Compose Bucket Idempotency (pull_request) Has been cancelled
CI / Unit & Component Tests (push) Failing after 2m3s
CI / OCR Service Tests (push) Successful in 16s
CI / Backend Unit Tests (push) Successful in 4m15s
CI / fail2ban Regex (push) Successful in 38s
CI / Compose Bucket Idempotency (push) Failing after 11s
- Extract makeFakePdfjsLib / makeFakeLibLoader to testHelpers.ts — single
  source of truth used by both PdfViewer.svelte.test.ts and
  usePdfRenderer.svelte.test.ts; removes the diverging-fidelity DRY violation
  flagged by @felixbrandt and @saraholt in the PR review
- Add 'loadDocument sets error and loading=false when getDocument().promise
  rejects' test to usePdfRenderer.svelte.test.ts — closes the error-path gap
  flagged by @felixbrandt and @saraholt
- Replace toBeInTheDocument() with toBeVisible() in the three absorbed
  spec-file tests — uniform assertion style across the loaded-state describe
  block, as flagged by @felixbrandt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 16:19:26 +02:00
Marcel
23cbb6be22 test(pdf-renderer): eliminate real pdfjs-dist loading from browser tests — use fake libLoader for all init() calls
Five tests in usePdfRenderer.svelte.test.ts called createPdfRenderer() without
a libLoader, causing init() to dynamically import pdfjs-dist in the browser.
Every dynamic import goes through Playwright's route handler, which calls
resolveManualMock via birpc to check for mocks. If the RPC closes during
teardown while one of these imports is in flight, the birpc race fires —
even though pdfjs-dist was never explicitly vi.mock()-ed.

Replace all bare createPdfRenderer() calls that invoke init() with
createPdfRenderer(makeFakeLibLoader()), identical to the pattern already
used in PdfViewer.svelte.test.ts. No real module loads, no route-handler
calls, no birpc exposure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 16:19:26 +02:00
Marcel
ef592ddd0c test(pdf-viewer): consolidate spec.ts into test.ts — absorb page-counter test, delete spec
Absorbs the three tests from PdfViewer.svelte.spec.ts (nav buttons, zoom
controls, page counter) into the loaded-state describe in test.ts, then
deletes the now-empty spec file. One spec file per component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 12:32:23 +02:00
Marcel
6c596babcb test(pdf-viewer): port PdfViewer.svelte.test.ts to libLoader prop injection — remove vi.mock
Removes both vi.mock('pdfjs-dist', …) calls that caused the birpc teardown
race (ADR 012). Replaces with static import + makeFakeLibLoader() helper
injected via the libLoader prop on every render() call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 12:32:23 +02:00
Marcel
ae1688319e test(annotation): replace synchronous query().toBeNull() with async not.toBeInTheDocument()
Svelte defers DOM updates to microtasks; .query() is a synchronous
snapshot that can fire before the element disappears — making the
absence assertions in AnnotationShape and AnnotationLayer non-deterministic.

Sweeps all 4 instances across both spec files (Sara's ≤5 threshold).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 10:21:24 +02:00
Marcel
a693f07eca refactor(test): convert TextLayerMock to class syntax in PdfViewer spec
Prototype-style assignment was a vi.mock hoisting artifact from the old
version of the file. Rest of the codebase uses class syntax — aligning.

Addresses Felix Brandt round-4 suggestion on PR #536.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
3ae7c9da0c test(pdf-renderer): guard init() against repeated calls — libLoader must fire once
Adds idempotency test: calling init() twice must invoke libLoader only once.
Adds `if (pdfjsReady) return;` guard to satisfy the contract.

Addresses Felix Brandt round-4 suggestion on PR #536.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
f126634804 refactor(test): remove what-comment from makeFakePdfjsLib
The name already implies it's a fake; the comment described what, not why.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
bdadff787c test(pdf-renderer): assert init() re-throws when libLoader rejects
.catch(()=>{}) swallowed the rejection, so the test passed vacuously even
if a future refactor silently caught the error. rejects.toThrow() proves
the propagation contract holds before asserting pdfjsReady stays false.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
f8dad85020 test(pdf-renderer): document libLoader rejection leaves pdfjsReady false
Regression-protection test: init() propagates the loader rejection
before pdfjsReady is set, so the renderer stays in a safe unready state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
5cd330de74 docs(pdf-viewer): comment untrack invariant on renderer init
Without untrack, a reactive libLoader prop reference change would
reinitialise the whole renderer and lose all loaded state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
06b158bf54 refactor(pdf-viewer): export LibLoader type and update callers
Exporting LibLoader gives the type a stable, named identity.
PdfViewer.svelte and PdfViewer.svelte.spec.ts now import it directly
instead of using Parameters<typeof createPdfRenderer>[0].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
073b6cb45d refactor(test): move PdfViewer import to top and annotate partial fake cast
- import PdfViewer left mid-file from vi.mock hoisting — no longer needed (Sara/Felix)
- adds one-line comment explaining as unknown as cast is an intentional partial fake (Felix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
115476453a fix(pdf-viewer): replace vi.mock(pdfjs-dist) with injected libLoader prop
Removes both vi.mock('pdfjs-dist', factory) and
vi.mock('pdfjs-dist/build/pdf.worker.min.mjs?url', factory) from
PdfViewer.svelte.spec.ts — the ManualMockedModule registrations that were
racing with vitest-browser-playwright's birpc teardown channel.

PdfViewer.svelte now accepts an optional libLoader prop (typed as
Parameters<typeof createPdfRenderer>[0]) that is passed untracked to
createPdfRenderer(). Tests supply a vi.fn() fake loader directly as a prop;
production code uses the default loader that imports the real pdfjs-dist.
The birpc route handler for pdfjs-dist is never registered, so no teardown
race is possible. Fixes #535.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
817ec44439 test(pdf-renderer): inject libLoader into createPdfRenderer to eliminate vi.mock factories
Adds an optional LibLoader parameter (defaults to the real pdfjs-dist dynamic
imports) and a failing test that verified the loader is called during init().
This is the first step toward removing ManualMockedModule registrations that
race with vitest-browser-playwright's birpc teardown (#535).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:57:28 +02:00
Marcel
eb666b2eb3 test(transcription): replace 3 setTimeout sleeps in TranscriptionEditView with vi.waitFor
Also replaces a vacuous expect(true).toBe(true) with a real behavioral
assertion that both block texts remain rendered after rerender.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
0e9d88eed4 test(document): replace 2 setTimeout sleeps in WhoWhenSection with vi.waitFor
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
29672c066b test(document): rewrite FileSwitcherStrip test with behavioral assertions
Replaces 3 setTimeout sleeps with vi.waitFor on document.activeElement
during keyboard nav, and converts 2 .not.toThrow smoke tests on the
prev/next buttons into no-op assertions: with a single file in the
strip the active chip stays selected and onSelect is not invoked.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
4045cec457 test(viewer): rewrite PdfViewer test with behavioral assertions
Replaces 6 setTimeout sleeps with vi.waitFor and expect.element
auto-wait, and converts 9 .not.toThrow smoke tests into assertions
on the rendered PDF nav controls (Zurück/Weiter/Vergrößern/Verkleinern)
and the conditional outdated-annotation notice / annotation visibility
toggle. transcribeMode test now mocks the annotations fetch so the
toggle button is actually rendered (annotationCount > 0 guard).

Runtime: 33s → 4.5s.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
27041a639d refactor(transcription): extract block CRUD into createTranscriptionBlocks hook
Pulls the transcription-block state (load, save, delete, reviewToggle,
markAllReviewed, createFromDraw, toggleTrainingLabel, deleteAnnotation
+ derived blockNumbers / hasBlocks / lastEditedAt / annotationReloadKey)
out of documents/[id]/+page.svelte into a reusable factory in
lib/document/transcription/useTranscriptionBlocks.svelte.ts.

The page now reads transcription.blocks / .blockNumbers / .hasBlocks /
.lastEditedAt / .annotationReloadKey reactively and delegates writes
to transcription.{load, save, delete, reviewToggle, markAllReviewed,
createFromDraw, toggleTrainingLabel, deleteAnnotation,
findByAnnotationId, bumpAnnotationReloadKey}. The confirm-then-delete
dialog stays in the page; the hook only handles the data ops.

24 unit tests cover initial state, load (success / non-OK / network /
empty-id), derived state (blockNumbers in sortOrder, lastEditedAt
recent-pick, lastEditedAt-null fallback), delete (success bumps key /
non-OK throws), reviewToggle (success updates / non-OK no-op), markAll
(success / non-OK), createFromDraw (success / non-OK / network all
return correct shape), toggleTrainingLabel (200 / 500), deleteAnnotation
(linked-block path / orphan-annotation path / orphan-fail throw),
findByAnnotationId match + miss, bumpAnnotationReloadKey.

Also bumps the polling-loop test waits in useOcrJob.svelte.test.ts to
150-200ms (from 60-80ms) so the suite is reliable when run in parallel.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
dd54ba9e74 test(viewer): more usePdfRenderer state branch coverage
renderCurrentPage early-returns when canvasEl/textLayerEl null,
init() idempotent on second call, zoomIn after floor, goToPage(1)
no-op.

5 new tests covering ~6 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
a53c656077 test(annotation): expand AnnotationLayer prop-combo coverage
Pointer hover events, multiple annotations with activeAnnotationId,
dimmed-overrides-faded, canDraw delete button, blockNumbers map.

5 new tests covering ~10 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
e5eedc17d0 test(viewer): cover PdfViewer outdated-annotation notice + fetch errors
Outdated notice when fileHash mismatches, no notice when matching,
fetch error gracefully ignored, non-OK response ignored.

4 new tests covering ~8 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
aa0c91cf76 test(transcription): expand TranscriptionEditView coverage
Adds activeAnnotationId reactive sync, mark-all-reviewed onclick,
all-blocks-rendered, next-block CTA, active vs inactive training
label chip styles.

6 new tests covering ~10 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
71940fc99a test(viewer): add more PdfViewer prop combinations
flashAnnotationId, blockNumbers, activeAnnotationId, combined with
transcribeMode, onAnnotationClick wired in.

5 new tests covering ~10 prop-combo branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
b38d555791 test(viewer): more usePdfRenderer state coverage
Mixed zoomIn/zoomOut return-to-start, zoomOut at floor no-op,
init() resolves and sets pdfjsReady, loadDocument with bogus URL
sets error and flips loading off.

4 new tests covering ~10 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
12733cb699 test(document): expand FileSwitcherStrip coverage
Adds prev/next button click safety, ArrowRight + ArrowLeft + ArrowDown
keyboard navigation through chips with wrap-around.

5 new tests covering ~10 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
16f5410c6f test(transcription): cover TranscriptionEditView branches
Empty-state coach, review progress counter, mark-all-reviewed
button visibility/disabled state, OcrTrigger conditional, training
labels visible/hidden by canWrite, label toggle callback, blocks
sorted by sortOrder.

12 tests covering ~30 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
9837d3b502 test(document): cover WhoWhenSection date and location branches
Date invalid state, error visibility before/after input, hidden ISO
output, location with initialLocation, location hidden in editMode,
FieldLabelBadge in editMode, required indicator.

7 tests covering ~10 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
99ca003f66 test(viewer): expand usePdfRenderer state coverage
Adds renderCurrentPage no-op when pdfjsLib uninitialized, prerender
no-op when pdfDoc null, destroy safe without document, setElements
no-throw, isLoaded false initially, accumulating zoomIn calls.

7 new tests covering ~10 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00
Marcel
f9b62982f6 test(viewer): cover PdfViewer empty and loaded state branches
Empty state when url is empty (no controls, placeholder shown),
loaded state with controls, annotationsDimmed branch, transcribeMode
flag, documentFileHash filtering branch.

6 tests covering ~10 branches.

Refs #496.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:50:28 +02:00