feat: PDF annotations for documents (#40) #54

Merged
marcel merged 10 commits from feat/40-pdf-annotations into main 2026-03-24 10:00:28 +01:00
Owner

Summary

  • Adds an annotation layer on top of the PDF.js viewer — admin users can draw highlight rectangles directly on PDF pages
  • Annotations are persisted to the backend (new DocumentAnnotation entity + REST endpoints) and reload on page visit
  • Admin users see an active Annotieren button; read-only users see a disabled one (permission: ANNOTATE_ALL)
  • Adds ANNOTATE_ALL permission to the Administrators group via Flyway migration

E2E fixes included

  • beforeAll hooks in the PDF viewer and annotation test suites now use the Playwright request fixture (direct API calls) instead of UI automation — eliminates 90 s timeout failures in CI
  • Fixed pointer-capture bug in AnnotationLayer.svelte: closest('[data-annotation]') replaces direct dataset.annotation check so delete buttons inside annotation divs fire correctly
  • DataInitializer: reader test user is created independently of the person-seed guard (was silently skipped when DB already had seed data)
  • Maven dev profile now activates dev,e2e Spring profiles so the E2E seed always runs locally

Test plan

  • All 21 E2E tests pass (npm run test:e2e)
  • Admin can draw, persist, reload, and delete annotations on a PDF
  • Reader user sees disabled Annotieren button
  • Backend unit tests pass (./mvnw test)

🤖 Generated with Claude Code

## Summary - Adds an annotation layer on top of the PDF.js viewer — admin users can draw highlight rectangles directly on PDF pages - Annotations are persisted to the backend (new `DocumentAnnotation` entity + REST endpoints) and reload on page visit - Admin users see an active **Annotieren** button; read-only users see a disabled one (permission: `ANNOTATE_ALL`) - Adds `ANNOTATE_ALL` permission to the Administrators group via Flyway migration ## E2E fixes included - `beforeAll` hooks in the PDF viewer and annotation test suites now use the Playwright `request` fixture (direct API calls) instead of UI automation — eliminates 90 s timeout failures in CI - Fixed pointer-capture bug in `AnnotationLayer.svelte`: `closest('[data-annotation]')` replaces direct `dataset.annotation` check so delete buttons inside annotation divs fire correctly - `DataInitializer`: reader test user is created independently of the person-seed guard (was silently skipped when DB already had seed data) - Maven dev profile now activates `dev,e2e` Spring profiles so the E2E seed always runs locally ## Test plan - [ ] All 21 E2E tests pass (`npm run test:e2e`) - [ ] Admin can draw, persist, reload, and delete annotations on a PDF - [ ] Reader user sees disabled Annotieren button - [ ] Backend unit tests pass (`./mvnw test`) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
marcel added 8 commits 2026-03-24 08:32:36 +01:00
- Install pdfjs-dist v5 and add optimizeDeps pre-bundle config
- New PdfViewer.svelte component: renders each page on a <canvas> with
  correct device-pixel-ratio scaling, overlays a text layer (enables
  text selection; foundation for annotations in #40), prev/next
  navigation, zoom controls, and lazy page rendering (only current ±1
  pre-fetched — avoids freezing on multi-page documents)
- Replace the <iframe> in documents/[id]/+page.svelte with PdfViewer;
  image attachments continue to use <img>; detection now uses
  doc.contentType instead of filename extension
- Unit tests for navigation controls and page counter (pdfjs mocked)
- E2E tests: PDF renders as canvas (not iframe), nav controls visible,
  image fallback stays as <img>; minimal.pdf fixture for upload tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Static import of pdfjs-dist fails during SSR because DOMMatrix and
other browser globals are unavailable in Node.js. Move the import into
onMount so it only ever executes in the browser. A plain pdfjsLib
variable holds the module; a $state boolean pdfjsReady triggers the
load-document effect once the library is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
devops: add rebuild-frontend.sh script
Some checks are pending
CI / Unit & Component Tests (push) Successful in 2m36s
CI / Backend Unit Tests (push) Successful in 2m3s
CI / E2E Tests (push) Has started running
0ef81e20f6
Stops the container, removes the stale node_modules volume, and
rebuilds the image. Run this after adding or updating npm dependencies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(frontend): zoom buttons now re-render immediately (#39)
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 4m42s
CI / Backend Unit Tests (pull_request) Successful in 2m18s
CI / E2E Tests (pull_request) Failing after 13m15s
CI / Unit & Component Tests (push) Successful in 2m20s
CI / Backend Unit Tests (push) Successful in 2m11s
CI / E2E Tests (push) Failing after 13m25s
ca5726e7c3
scale was only read inside the async renderPage function, so Svelte 5
never tracked it as a reactive dependency of the effect. Reading scale
synchronously in the effect condition registers it as a dependency and
triggers a re-render on every zoom change.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- Add ANNOTATE_ALL permission
- Add ANNOTATION_NOT_FOUND and ANNOTATION_OVERLAP error codes
- V10 migration: document_annotations table with page/rect/color/owner
- DocumentAnnotation entity, AnnotationRepository, CreateAnnotationDTO
- AnnotationService: overlap detection (rectangle intersection), ownership enforcement on delete
- AnnotationController: GET (authenticated), POST/DELETE (ANNOTATE_ALL)
- 15 new tests (AnnotationServiceTest, AnnotationControllerTest) — TDD red/green

Frontend:
- AnnotationLayer.svelte: pointer-event drawing, colored rect overlays, delete buttons
- PdfViewer.svelte: annotate toggle, color picker, loads/saves/deletes annotations via API
- Disabled annotate button with tooltip for users without ANNOTATE_ALL
- canAnnotate exposed from layout server, passed to PdfViewer
- errors.ts + de/en/es translations for new error codes
- 3 new unit tests for AnnotationLayer — TDD red/green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add e2e to the dev Maven profile's spring.profiles.active so
  DataInitializer always runs when developing/testing locally
- Create the reader test user independently of the person-seed guard
  so it survives restarts where seed data already exists
- Set SPRING_PROFILES_ACTIVE=dev,e2e in docker-compose backend service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a child element inside an annotation div (e.g. the delete button)
was clicked, the AnnotationLayer's pointerdown handler would call
setPointerCapture, preventing the child's click event from firing.
Using closest('[data-annotation]') instead of checking dataset.annotation
on the target directly fixes delete buttons inside annotation elements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
test(e2e): rewrite PDF viewer and annotation beforeAll to use API calls
Some checks failed
CI / E2E Tests (pull_request) Waiting to run
CI / Unit & Component Tests (push) Successful in 2m27s
CI / Backend Unit Tests (push) Successful in 2m17s
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (push) Has started running
05f3ce687f
- Replace UI-based document setup in beforeAll hooks with direct API
  calls via Playwright's request fixture — avoids the 90s timeout from
  navigating + uploading through the Docker dev server
- Fix non-PDF test: create a file-less document in beforeAll instead of
  relying on seed data that may not exist
- Share annotationDocId across describe blocks so the read-only user
  test can navigate to a known PDF document
- Add annotation visibility check before enabling annotate mode in the
  delete test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel added 1 commit 2026-03-24 08:50:29 +01:00
fix(frontend): make annotation delete button fully opaque
Some checks failed
CI / Backend Unit Tests (pull_request) Successful in 2m15s
CI / E2E Tests (pull_request) Successful in 22m58s
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Successful in 2m29s
eb8bcdb426
Replace opacity: 0.3 on the annotation container with an rgba
background so child elements (the × button) are not affected by
the parent's opacity and render at full opacity.

Refs #40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel added 1 commit 2026-03-24 08:52:59 +01:00
feat(db): add migration to grant ANNOTATE_ALL to existing admin groups
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 2m27s
CI / Backend Unit Tests (pull_request) Successful in 2m12s
CI / E2E Tests (pull_request) Successful in 23m43s
CI / Unit & Component Tests (push) Successful in 2m28s
CI / Backend Unit Tests (push) Successful in 2m15s
CI / E2E Tests (push) Successful in 22m17s
37f5c3d005
Covers existing deployments where the Administrators group was created
before DataInitializer started including ANNOTATE_ALL.

Refs #40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marcel merged commit 37f5c3d005 into main 2026-03-24 10:00:28 +01:00
marcel deleted branch feat/40-pdf-annotations 2026-03-24 10:00:31 +01:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#54