Commit Graph

1353 Commits

Author SHA1 Message Date
Marcel
0344a0c7ff feat(backend): dispatch thumbnail generation from MassImportService
ODS/Excel imports that actually upload a file (file.isPresent()) now
trigger thumbnail generation alongside hash/metadata. Metadata-only
import rows produce no thumbnail — nothing to render.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:58:24 +02:00
Marcel
7d0e13c591 feat(backend): dispatch thumbnail generation from DocumentService upload paths
All four upload code paths (storeDocument, createDocument, updateDocument,
attachFile) now call thumbnailAsyncRunner.dispatchAfterCommit(id) after
the document save. createDocument and updateDocument only dispatch when a
file was actually provided/replaced.

The dispatch is afterCommit-safe: if the surrounding @Transactional
method rolls back, no thumbnail is generated for a document that never
reached the DB.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:57:36 +02:00
Marcel
3b7ef6117e feat(backend): add ThumbnailAsyncRunner with afterCommit dispatch and timeout
Bridges @Transactional upload paths to the async thumbnail pipeline.
dispatchAfterCommit registers a TransactionSynchronization so the async
task only fires after the surrounding commit (and is silently skipped
on rollback) — mirrors the AuditService.logAfterCommit pattern.

generateAsync wraps the full ThumbnailService.generate call in a 30s
watchdog so a hung PDFBox render cannot occupy a thumbnailExecutor slot
indefinitely.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:49:26 +02:00
Marcel
955c497ba0 feat(backend): add ThumbnailService for PDF and image thumbnails
Renders a 240px-wide JPEG (quality 85) from either a PDF first page
via PDFBox or a JPEG/PNG/TIFF scan via ImageIO, then uploads to
S3 under thumbnails/{docId}.jpg and updates the Document entity.

Scaling uses Graphics2D.drawImage with VALUE_INTERPOLATION_BILINEAR
(not deprecated Image.getScaledInstance). Source is streamed via
FileService.downloadFileStream to avoid buffering 50MB PDFs.

Never throws — returns Outcome.SKIPPED for unsupported content types
and Outcome.FAILED for rendering/upload errors so the backfill can
tally them without aborting the run.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:46:08 +02:00
Marcel
0bb18c6789 feat(backend): add thumbnailExecutor bean for isolated thumbnail workload
Dedicated thread pool (core=1, max=2, queue=200) with CallerRunsPolicy
for back-pressure. Keeps thumbnail rendering off the shared taskExecutor
used by OCR and out of the AbortPolicy queue that drops work on overflow.
Quick-upload batches (15+ files) now apply back-pressure instead of
silently dropping thumbnail jobs.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:42:05 +02:00
Marcel
07019f54e8 feat(backend): add FileService.downloadFileStream for memory-efficient reads
Thumbnail generation will call this for PDFs up to 50 MB — loading the
full byte[] via downloadFileBytes would cause real memory pressure on
the single-VPS deploy. Stream-based reads let PDFBox parse the first
page without holding the whole file in heap.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:41:15 +02:00
Marcel
2aa3b955f9 build: add twelvemonkeys-imageio-tiff for thumbnail TIFF support
JDK ImageIO handles JPEG, PNG, BMP, GIF out of the box but not TIFF.
Since the document upload allowlist permits image/tiff, the thumbnail
generator must also decode it.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:37:05 +02:00
Marcel
a2333975f9 feat(backend): add THUMBNAIL_BACKFILL_ALREADY_RUNNING error code
Mirrors the IMPORT_ALREADY_RUNNING pattern for the concurrent-start
guard in ThumbnailBackfillService.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:36:06 +02:00
Marcel
b8962f4337 feat(backend): add DocumentRepository finder for thumbnail backfill
Adds findByFilePathIsNotNullAndThumbnailKeyIsNull() used by the
upcoming ThumbnailBackfillService to locate documents that have a
file attached but no thumbnail yet.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:35:08 +02:00
Marcel
6cf0601590 feat(db): add thumbnail_key and thumbnail_generated_at to documents
Adds two nullable columns to the documents table and their JPA mappings
on the Document entity. Both are left out of the OpenAPI required-mode
schema so the generated TypeScript type exposes them as optional.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 21:34:03 +02:00
Marcel
dd6cbe3a6f docs(specs): add final specs for thumbnail rows + person dashboard
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m41s
CI / OCR Service Tests (push) Successful in 33s
CI / Backend Unit Tests (push) Failing after 2m48s
Two production-ready specs following the chronik-spec format
(scaled wireframes × 3 viewports + impl-ref tables with exact Tailwind
classes and pixel values + WCAG contrast verification):

- briefwechsel-thumbnail-rows-spec.html — /briefwechsel row redesign
  with PDF thumbnail, summary-as-quote, bilateral distribution bar;
  drops status lifecycle and script-type indicators.

- person-dashboard-spec.html — new Korrespondenz-Überblick block on
  /persons/[id] with stats, activity histogram, direction split, top
  correspondents/locations, tag cloud. Every tile deep-links to
  /briefwechsel with filters.

Both specs share the DistributionBar.svelte component.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 20:17:21 +02:00
Marcel
f5438c4c36 docs(specs): add briefwechsel-fill — 5 concepts exploring empty-row problem
Brainstorming artifact: 5 HTML mockups comparing approaches to fill the
sparse right-hand space on /briefwechsel rows (reported by users as
"feels empty"):

  1. Rich Rows — dense metadata, no images
  2. Thumbnail Rows — PDF preview on the left
  3. Master-Detail Split — list + persistent preview panel
  4. Gallery Cards — grid of letter cards, album style
  5. Person Dashboard — insights live on /persons/[id], not here

Picked: #2 (Thumbnail Rows) + #5 (Person Dashboard), followed up by
final specs in separate commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 20:16:20 +02:00
Marcel
ac2118db14 fix(notifications): replace aggressive EventSource close with threshold-based 401-aware retry
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m36s
CI / OCR Service Tests (push) Successful in 31s
CI / Backend Unit Tests (push) Failing after 2m51s
On CLOSED readyState, probes session and redirects to /login only on 401.
On CONNECTING, counts consecutive errors and closes + probes only after 3
failures, preventing infinite retries without killing transient reconnects.

Closes #203

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 15:41:39 +02:00
Marcel
82de629339 fix(nav): prevent global header overflow at lg breakpoint (1024px)
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m37s
CI / OCR Service Tests (push) Successful in 35s
CI / Backend Unit Tests (push) Failing after 2m53s
- Upload button text wrapped in hidden xl:inline to hide label below xl
- AppNav logo margin reduced from mr-10 to mr-4 xl:mr-10 at lg breakpoint
  Combined these changes bring the header content to ~923px vs ~945px
  available space at 1024px, eliminating horizontal overflow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 14:35:25 +02:00
Marcel
2b8032524d fix(nav): restore DocumentTopBar back button sizing and add right padding
- BackButton gains showLabel prop: showLabel=false renders icon-only with
  aria-label, no mr-2 on svg (was causing 0px button width in topbar)
- DocumentTopBar: BackButton restored to h-11 w-11 circular touch target
  with showLabel=false matching the original 44×44px <a> it replaced
- Topbar row gets pr-4 (16px right padding per spec); action buttons div
  no longer needs its own pr-3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 14:34:31 +02:00
Marcel
261f631318 test(nav): add E2E test for DocumentTopBar back navigation; fix auth.setup login label
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m38s
CI / OCR Service Tests (pull_request) Successful in 30s
CI / Backend Unit Tests (pull_request) Failing after 2m53s
CI / Unit & Component Tests (push) Failing after 2m36s
CI / OCR Service Tests (push) Successful in 34s
CI / Backend Unit Tests (push) Failing after 2m47s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 12:08:31 +02:00
Marcel
9e59da598e fix(nav): replace static href="/" in DocumentTopBar with <BackButton>
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m46s
CI / OCR Service Tests (pull_request) Successful in 39s
CI / Backend Unit Tests (pull_request) Failing after 3m2s
CI / Unit & Component Tests (push) Failing after 2m46s
CI / OCR Service Tests (push) Successful in 27s
CI / Backend Unit Tests (push) Failing after 2m44s
The document detail page back button was missed in the original refactor —
it still pointed to "/" (dashboard) regardless of where the user came from.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 11:27:03 +02:00
Marcel
367dcc66f2 refactor(nav): add class prop to BackButton, remove mb-4 from topbar contexts
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m41s
CI / OCR Service Tests (push) Successful in 37s
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 2m44s
CI / OCR Service Tests (pull_request) Successful in 32s
CI / Backend Unit Tests (pull_request) Failing after 2m51s
- BackButton now accepts a `class` prop (default 'mb-4') so callers can
  override spacing; resolves hardcoded margin in flex-row topbar snippets
- documents/[id]/edit and enrich/[id] pass class="" to suppress the margin
- Replace weak className unit test with class-prop behaviour tests
- Add [data-hydrated] comment in E2E spec explaining what emits the attribute

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 11:16:49 +02:00
Marcel
6c99c6a670 test(nav): add E2E tests for BackButton navigation and accessibility
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m47s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Unit & Component Tests (push) Failing after 2m39s
CI / OCR Service Tests (push) Successful in 40s
CI / Backend Unit Tests (push) Failing after 2m52s
CI / Backend Unit Tests (pull_request) Failing after 3m7s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 11:00:56 +02:00
Marcel
ae3bc3f246 docs(claude): update back link pattern to use BackButton component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 10:50:35 +02:00
Marcel
cc9c47254b refactor(nav): replace static back-link hrefs with BackButton
All 7 in-scope back navigation links converted to use history.back().
Admin panel mobile chevron converted inline (icon-only, different
visual pattern). Cancel buttons left as static <a> links.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 10:49:23 +02:00
Marcel
781c4ffebb feat(nav): add BackButton component calling history.back()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 10:46:41 +02:00
Marcel
fd93f1a4da feat(chronik): rename route and heading to Aktivitäten
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m48s
CI / OCR Service Tests (push) Successful in 31s
CI / Backend Unit Tests (push) Failing after 2m43s
/chronik → /aktivitaeten; heading updated in all three locales.
Component folder (lib/components/chronik/) stays unchanged — internal
implementation detail, not user-facing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 09:28:09 +02:00
Marcel
4f671824dd feat(chronik): align layout to grouped card pattern; fix duplicate rollup count
Some checks failed
CI / Unit & Component Tests (push) Failing after 3m31s
CI / OCR Service Tests (push) Successful in 57s
CI / Backend Unit Tests (push) Failing after 3m0s
- ChronikTimeline: date buckets now render as bordered cards with muted
  header (border-line / bg-surface / shadow-sm) and divide-y row
  separators, matching the DocumentList card pattern
- ChronikRow: remove rounded-sm (card handles clipping), hover:bg-canvas
  → hover:bg-muted/50; restore rollup count badge after doc title
- Messages (de/en/es): remove embedded {count} from all four rollup verb
  strings so the badge is the single source of truth, consistent with
  DashboardActivityFeed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 09:13:03 +02:00
Marcel
ba0f9bb325 refactor(test): fix unchecked cast and derive ALL_ELIGIBLE_KINDS from enum
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m36s
CI / OCR Service Tests (pull_request) Successful in 29s
CI / Backend Unit Tests (pull_request) Failing after 2m46s
CI / Unit & Component Tests (push) Failing after 2m43s
CI / OCR Service Tests (push) Successful in 31s
CI / Backend Unit Tests (push) Failing after 2m51s
- Replace any(Set.class) with any() to eliminate the raw-type unchecked
  cast in DashboardControllerTest
- Derive ALL_ELIGIBLE_KINDS from AuditKind.ROLLUP_ELIGIBLE.stream() so
  the integration test constant stays in sync with the production constant
  automatically when new kinds are added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 22:45:23 +02:00
Marcel
c0e52b8432 refactor(audit): move AuditLogQueryServiceTest to audit package
Addresses review concern: the test lived in the dashboard package but
tests the audit domain service. Package-by-feature convention requires
audit tests to live in the audit package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 22:44:37 +02:00
Marcel
42cf7715d2 test(chronik): extract applyClientFilter helper with full test coverage
Addresses review concern: the fuer-dich predicate (youMentioned ||
youParticipated) had zero test coverage after feedFilters.test.ts was
deleted. The new clientFilter module is a pure function that is directly
testable, and the test explicitly documents why MENTION_CREATED items
without the youMentioned flag are now excluded (they would have shown
mentions directed at OTHER users under the old feedFilters.ts logic).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 22:43:52 +02:00
Marcel
330c6227bc refactor(chronik): remove client-side filter; add aria-live/aria-busy
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m51s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 3m3s
CI / Unit & Component Tests (pull_request) Failing after 2m39s
CI / OCR Service Tests (pull_request) Successful in 28s
CI / Backend Unit Tests (pull_request) Failing after 2m50s
- Delete feedFilters.ts and its 9 tests (dead code: server now filters)
- Remove activeFilter $state + $effect — read data.filter directly
- fuer-dich stays client-side via youMentioned/youParticipated predicate
- aria-live="polite" + aria-busy={!!navigating.type} on timeline region

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 22:26:27 +02:00
Marcel
d42293d3f5 feat(chronik): pass kinds query param from filter pill to API
Each filter pill maps to a specific set of AuditKinds sent as
?kinds= to /api/dashboard/activity. fuer-dich omits kinds so the
server returns all eligible events; client-side predicate on
youMentioned/youParticipated handles the final narrowing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 22:20:45 +02:00
Marcel
99c3106835 feat(openapi): expose kinds param in dashboard activity spec
Added @Parameter annotation so SpringDoc renders kinds as an
enum-array query param; regenerated TypeScript API types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:19:16 +02:00
Marcel
8d16e4d975 feat(dashboard): add kinds param to GET /api/dashboard/activity
Spring auto-converts ?kinds=FILE_UPLOADED,TEXT_SAVED to Set<AuditKind>.
Absent or empty kinds defaults to ROLLUP_ELIGIBLE. Unknown value → 400.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:10:43 +02:00
Marcel
571ecfc626 test(dashboard): guard getPulse always uses 2-arg findActivityFeed
Regression test: getPulse must never receive a kinds filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:05:43 +02:00
Marcel
475e16a85d feat(audit): add findActivityFeed(UUID, int, Set<AuditKind>) overload
Two-arg variant delegates to three-arg with ROLLUP_ELIGIBLE so
existing callers (getPulse) are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 21:03:39 +02:00
Marcel
fe7a8ed9ad feat(audit): add kinds param to findRolledUpActivityFeed
Filter is applied at the innermost events CTE to reduce rows
entering the LAG/session CTEs. Existing callers pass ROLLUP_ELIGIBLE
by default so behaviour is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 20:59:56 +02:00
Marcel
d700b0a948 refactor(audit): add ROLLUP_ELIGIBLE constant to AuditKind
Single source of truth for the six kinds eligible for the activity rollup feed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 20:53:14 +02:00
Marcel
7d9c7f1357 chore(api): mark manually-patched fields for next regen cycle
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m47s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Backend Unit Tests (pull_request) Failing after 2m47s
CI / Unit & Component Tests (push) Failing after 2m54s
CI / OCR Service Tests (push) Successful in 39s
CI / Backend Unit Tests (push) Failing after 2m59s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 18:38:16 +02:00
Marcel
a76af739e5 test(notification-bell): cover handleMarkRead annotationId and commentId-only paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 18:37:18 +02:00
Marcel
a15e4e139b test(chronik-row): add coverage for commentId-only URL when annotationId absent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 18:34:41 +02:00
Marcel
e175e050f9 feat(chronik-row): deep-link COMMENT_ADDED and MENTION_CREATED to comment
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m55s
CI / OCR Service Tests (push) Successful in 37s
CI / Backend Unit Tests (push) Failing after 2m50s
CI / Unit & Component Tests (pull_request) Failing after 2m38s
CI / OCR Service Tests (pull_request) Successful in 34s
CI / Backend Unit Tests (pull_request) Failing after 2m51s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:38:50 +02:00
Marcel
95c11b9b46 feat(chronik-fuer-dich): include annotationId in mention deep-link
Sidebar was constructing /documents/:id?commentId=… without the
annotationId, so clicking a mention there no-op'ed the deep-link
scroll helper. Route the href through buildCommentHref so the
bell and the chronik sidebar produce identical URLs.

Refs #300.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:21:16 +02:00
Marcel
7c22e42b8f refactor(notification-bell): use buildCommentHref helper
Drops the inline conditional href construction in favour of the
shared helper. Identical URL shape — behaviour preserved.

Refs #300.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:17:57 +02:00
Marcel
7f40c54b3f feat(utils): add buildCommentHref helper for comment deep-links
Single source of truth for constructing /documents/:id?commentId=…
(&annotationId=…) URLs. Used by the notification bell, the chronik
"Für dich" sidebar, and the chronik main feed so the three surfaces
can no longer diverge.

Refs #300.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:15:48 +02:00
Marcel
76a3a2e04c chore(api): hand-edit generated types for commentId + annotationId
Adds the two new optional fields on ActivityFeedItemDTO in the
generated openapi-typescript output. Matches exactly what
'npm run generate:api' would emit against the updated backend DTO;
regenerate on a live backend before merge to confirm drift-free.

Refs #300.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:13:56 +02:00
Marcel
f50a746619 feat(dashboard): enrich activity feed DTO with commentId + annotationId
ActivityFeedItemDTO gains nullable commentId and annotationId fields.
DashboardService.getActivity forwards commentId from the projection
and batch-resolves annotationId via the new
CommentService.findAnnotationIdsByIds lookup. Both remain null for
non-comment kinds, so the bulk lookup is skipped entirely when the
feed has no comment rows.

Refs #300.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:11:03 +02:00
Marcel
40260be07a feat(comment): add findAnnotationIdsByIds batch lookup
Exposes a CommentService method that maps a collection of
commentIds to their annotationIds via commentRepository.findAllById.
Unknown comments and comments with null annotationId are omitted.
Used by the dashboard activity feed enrichment to supply the
deep-link annotationId without growing the audit SQL query.

Refs #300.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:04:38 +02:00
Marcel
b9f5ec22aa feat(audit): expose commentId on rolled-up activity feed projection
Adds getCommentId() to ActivityFeedRow and selects
(ag.payload->>'commentId')::uuid from findRolledUpActivityFeed so
chronik consumers can build deep-link URLs for COMMENT_ADDED and
MENTION_CREATED events. Null for other kinds.

Refs #300.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:00:36 +02:00
Marcel
b07f9efa9c 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>
2026-04-21 15:22:38 +02:00
Marcel
3bf0b38c42 polish(document-detail): address review concerns on onMount deep-link wiring
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m40s
CI / OCR Service Tests (pull_request) Successful in 30s
CI / Backend Unit Tests (pull_request) Failing after 2m44s
CI / Unit & Component Tests (push) Failing after 2m41s
CI / OCR Service Tests (push) Successful in 37s
CI / Backend Unit Tests (push) Failing after 2m55s
Three small refinements from Felix's review cycle 1:

- replaceState(page.url.pathname, page.state ?? {}) — defend against
  first-navigation cases where page.state can be undefined.
- Extract the inline tick + requestAnimationFrame into a named
  waitForPanelRender() helper; intent is now readable from onMount.
- Attach .catch() to the fire-and-forget scrollToCommentFromQuery
  promise so any helper throw surfaces via console.error instead
  of silently disappearing.

No behavior change on the happy path. All existing tests stay green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 14:10:46 +02:00
Marcel
567faee3cc test(e2e): notification deep-link scrolls to target comment
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m43s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 3m0s
CI / Unit & Component Tests (pull_request) Failing after 2m46s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Backend Unit Tests (pull_request) Failing after 2m45s
Seeds a document, transcription block, and block comment via API,
then visits /documents/{id}?commentId=X&annotationId=Y and asserts
the page enters transcribe mode, the comment article becomes visible,
and the URL query params are stripped. Runs at 320px and 1440px so
the collapsed PDF strip clipping on mobile is caught. An axe-core
pass guards the new tabindex + focus-visible ring against a11y
regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:44:45 +02:00
Marcel
e22265f5bc feat(document-detail): wire notification deep-link scroll in onMount
After navHeight setup, call scrollToCommentFromQuery with the page
URL and callbacks into the component's local state (transcribeMode,
activeAnnotationId, flashAnnotationId) plus SvelteKit's replaceState
to strip the consumed query params.

afterTick awaits both Svelte's tick() and one requestAnimationFrame,
mirroring the existing handleAnnotationClick timing so the annotation
panel has rendered before scrollIntoView fires.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:42:55 +02:00