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>
Replaces hardcoded 'Menü öffnen'/'Menü schließen' ternary with
m.layout_menu_open()/m.layout_menu_close() so the mobile nav toggle
announces correctly in EN and ES locales.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Adds 7 Paraglide keys (viewer_previous_page, viewer_next_page,
viewer_zoom_out, viewer_zoom_in, transcribe_mark_for_training,
layout_menu_open, layout_menu_close) to de/en/es.json.
Adds messages.spec.ts to enforce key parity across all three locales.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The dep update resolved @playwright/test and playwright to 1.60.0.
The CI container was pinned to v1.58.2-noble which lacks the matching
browser binary, causing the browser project to fail to launch and
coverage thresholds to hit 0%.
Also raises @playwright/test and playwright lower bounds in package.json
to ^1.60.0 to keep the declared range consistent with the lockfile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The backport of vitest PR #10267 (unroute-before-register guard that
prevents orphan routes causing birpc teardown crashes) was made against
4.1.0. The dep bump moved the package to 4.1.6; patch-package refused to
apply the stale file. Regenerated against the installed 4.1.6 — the fix
is identical, adapted for the renamed idPreficates → idPredicates typo
that upstream corrected in this version.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bumps declared semver ranges to the patched minimums so a fresh
npm install (without the lockfile) cannot resolve to a vulnerable
version:
@sveltejs/adapter-node ^5.4.0 → ^5.5.4
@sveltejs/kit ^2.48.5 → ^2.60.1
vite ^7.2.2 → ^7.3.3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
npm update caused @tiptap/starter-kit@3.22.5 to nest @tiptap/core@3.23.4
alongside the pinned top-level 3.22.5, splitting the type namespace and
causing svelte-check errors (toggleBold, toggleItalic, etc. not found).
Aligning all three pinned tiptap packages to 3.23.4 collapses the nested
copy via deduplication, restoring the pre-bump error count (792 = main).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blocks merges when any HIGH or CRITICAL advisory enters the production
dependency tree. Runs after npm ci (or cache restore) and before lint,
so a failing audit surfaces immediately without wasting test time.
Closes the systemic gap from pre-prod audit finding F-22 (dependency
hygiene). Renovate automation is tracked separately.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Makes the upload size cap explicit in both dev and prod compose files.
After the @sveltejs/kit bump (GHSA-2crg-3p73-43xp), the default 512KB
limit is now enforced — 50M covers multi-page Kurrent/Sütterlin PDFs
(typically 500KB–15MB) without being reckless.
Caddy's client_max_body_size must be set to match when the reverse
proxy config is committed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After adding @Schema(requiredMode=REQUIRED) to InviteListItemDTO.shareableUrl,
npm run generate:api now emits shareableUrl as required. Replace the hand-rolled
InviteListItem interface with a type alias to the generated InviteListItemDTO
and remove the two 'as unknown as InviteListItem' casts + TODO comments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix VALID_STATUSES fallback to use uppercase enum value
- Add TODO comment on InviteListItem cast pending type regeneration
- Guard revoke action against null id (returns fail 400)
- Add request: to delete action mock events for Sentry consistency
- Add expiresAt forwarding test for create action
- Add null-id guard test for revoke action
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add load() unit tests for admin/users/[id] (permission gate, 404, success)
- Rename .test.ts → .spec.ts for consistency with rest of suite
- Add @Schema(requiredMode=REQUIRED) to InviteListItem.shareableUrl
- Add client-side allowlist for invite status query param
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`@sentry/sveltekit` wraps load functions and reads `event.request.method` and
`event.url.pathname`. Mock events that omitted `request` or `url` threw
`TypeError: Cannot read properties of undefined` on every invocation, silently
masking 86 test failures on main.
Two root causes fixed:
- Added `request: new Request(...)` (and `url: new URL(...)` where absent) to
all mock event objects in 14 `*.server.spec.ts` files
- Changed `;` to `&&` in the `test:coverage` npm script so a failing server
run propagates its exit code instead of being swallowed by the client run
All 576 server-project tests now pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The multipart note previously said "use raw fetch" which was misread
as "global fetch is acceptable". Clarify that event.fetch must always
be used — the typed client is bypassed for multipart, but handleFetch
still needs to inject the session cookie.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace fetch('/api/users/${id}', { method: 'PUT', ... }) + inline JSON
error parsing with createApiClient(fetch).PUT('/api/users/{id}', ...) and
the standard result.error cast pattern.
Also fix pre-existing Sentry mock event failures in layout.server.spec.ts
by adding request and url to the test event object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace manual fetch(${apiUrl}/api/...) calls in load, create, and revoke
with createApiClient(fetch) so auth injection is handled by handleFetch
and the typed API contract is enforced at compile time.
Also fix pre-existing load test failures caused by Sentry's load wrapper
reading event.request.method (add request to the mock event object).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add min-h-[44px] py-2 to <summary> in ImportStatusCard for 44 px touch target
- Add SkippedFile and skipped count entries to docs/GLOSSARY.md
- Add MassImportServiceTest case: ALREADY_EXISTS fires before file I/O when doc is UPLOADED and file is present on disk
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change importSingleDocument return type from boolean to Optional<String>
so callers in processRows receive the skip reason on every non-success path.
S3 upload failures now surface as "S3_UPLOAD_FAILED" and already-imported
documents as "ALREADY_EXISTS" in the skippedFiles list shown in the admin UI.
- Add two new tests: runImportAsync_addsS3UploadFailed_toSkippedFiles and
runImportAsync_addsAlreadyExists_toSkippedFiles; update
importSingleDocument_skips_whenDocumentAlreadyUploadedNotPlaceholder and
the S3-failure test to assert on the Optional return value.
- Add i18n keys for S3_UPLOAD_FAILED and ALREADY_EXISTS in de/en/es messages.
- Svelte ImportStatusCard: add aria-hidden="true" to SVG chevron, wrap
conditional warning section in aria-live="polite" div, add max-h-64
overflow-y-auto to skipped-files <ul> to cap height on large batches.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add comment to openFileStream() explaining package-private visibility
is intentional (Mockito spy seam for IOException test)
- Key {#each} skippedFiles by filename instead of array index
- Add test: skipped section hidden when state is FAILED
- Add test: reasonLabel returns raw code for unknown reason strings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use loop index as each key (handles duplicate filenames)
- Increase skipped filename font from text-xs to text-sm
- Add motion-safe guard to details chevron transition
- Replace text-warning with text-amber-900 to meet WCAG AA contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add @Schema(requiredMode = REQUIRED) to SkippedFile and ImportStatus
record components so TypeScript codegen produces non-optional fields
when generate:api is next run
- Extract openFileStream(File) as package-private method so the
IOException path can be tested deterministically without relying on
OS-level file permissions (which are bypassed when running as root)
- Replace assumeTrue-based IOException test with Mockito spy that stubs
openFileStream — test now runs in CI unconditionally (45 tests, 0 skipped)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setReadable(false) silently no-ops as root; check canRead() to guard
the assumption correctly so the test is skipped in Docker CI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- remove duplicate List import in AdminControllerTest
- derive skipped() from skippedFiles.size() — drop redundant int field
- use machine codes for SkippedFile.reason (INVALID_PDF_SIGNATURE, FILE_READ_ERROR)
- map reason codes to i18n strings in ImportStatusCard (de/en/es)
- replace raw amber Tailwind classes with warning semantic token
- fix <summary> accessibility: replace list-none with rotating chevron SVG
- replace <p> with <span> inside <summary> (phrasing content rule)
- extract setupOneValidOneFakeImport() helper — remove 3x copy-paste
- add lenient mock to short-file test for defensive coverage
- add IOException path test for isPdfMagicBytes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds SkippedFile to the local ImportStatus type and updates
ImportStatusCard to show an amber skipped-count section with a
collapsible filename list in the DONE state. Only rendered when
skipped > 0. i18n keys added for de/en/es.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reads first 4 bytes of each candidate file before upload; rejects any
file whose header does not match %PDF (0x25 0x50 0x44 0x46). Skipped
files are counted and collected in ImportStatus.skippedFiles so
operators can see what was rejected without querying Loki.
Breaking: ImportStatus record gains skipped + skippedFiles fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add @BatchSize(50) fallback comments on findBySenderId / findByReceiversId
- Replace silent size() discard in getRecentActivity test with assertThat isNotEmpty()
- Add ADR-022 reference comment above @JsonIgnoreProperties on Person and Tag
- Document within-open-transaction limitation in DocumentLazyLoadingTest Javadoc
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Refactor DocumentLazyLoadingTest: pull value assertions (assertThat) out
of assertThatCode lambdas so failures surface as AssertionError rather
than "unexpected exception: AssertionError" (review item 1)
- Add @EntityGraph("Document.full") to findBySenderId, findByReceiversId,
findConversation, and findSinglePersonCorrespondence — all return full
Documents to the controller for JSON serialization (review item 2)
- Add "// Callers access only ..." comments to un-graphed methods where no
lazy associations are touched: findByTags_Id, findByStatus,
findByMetadataCompleteFalse(Sort), findByMetadataCompleteFalse(Pageable)
- Remove "what" inline comments from @Transactional(readOnly=true)
on getRecentActivity and getDocumentById — the why is in ADR-022 (item 4)
- Add named-graph coupling consequence to ADR-022: Document.java and
DocumentRepository.java graph name strings must stay in sync (item 5)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The convention 'read methods are not annotated' has one exception: methods
that return lazily-initialized entities to callers require readOnly=true to
keep the session open. Documents the rule and links to ADR-022.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace repeated personRepository.save/tagRepository.save/documentRepository.save
boilerplate with savedPerson(), savedTag(), savedDocument() helpers.
Each test body is now 2-3 lines of relevant setup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
List<Document> findAll(Specification) is called in DocumentService for
receiver-sort, sender-sort, and conversation queries but had no query-count
coverage. Asserts ≤5 statements for 5 docs with @EntityGraph(Document.list).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
assertThatCode(() -> service.searchDocuments(...)) passed vacuously on an
empty page; capture the result, assert totalElements > 0, then assert
getSender().getLastName() is accessible post-return.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without setStatisticsEnabled(true) the counter stays 0 and ≤2 passes
vacuously when the test runs in isolation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hibernate throws AnnotationException at startup when @BatchSize is placed
on a @ManyToOne field. @BatchSize is only valid on collections (@OneToMany,
@ManyToMany, @ElementCollection). The N+1 for sender is already covered by
the @EntityGraph overrides on DocumentRepository.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous version only asserted the method call didn't throw. Now the test
captures the returned list and asserts that sender.getLastName() and
tags.size() are accessible outside the transaction, which is the scenario
that would have failed with a LazyInitializationException.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These annotations deviate from the project convention (read methods are
normally unannotated). The comment explains that the session must stay
open for callers to access lazy-loaded collections post-return, preventing
future developers from removing the annotation as a cleanup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consistent with the @BatchSize already on receivers and tags. Any lazy
code path not covered by an entity graph will batch-load these associations
instead of issuing one query per document.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getRecentActivity calls findAll(Pageable) — the JpaRepository overload
not covered by the existing Specification variants. Without this override,
sender is loaded N+1 per document. Now applies Document.list graph so
sender and tags are fetched eagerly for every findAll(Pageable) call.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds failing test: findAll(Pageable) must not N+1 sender for 5 docs.
Without @EntityGraph override for this overload, each document triggers
a separate SELECT for its lazy sender.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Stats tracking is already enabled per-test via setStatisticsEnabled(true);
enabling it globally added unnecessary overhead to every test in the suite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five integration tests verify that DocumentService and DashboardService
do not throw LazyInitializationException after the EAGER→LAZY migration:
getDocumentById, getRecentActivity, searchDocuments (receiver/sender sort),
and dashboardService.getResume.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- getDocumentById: add @Transactional(readOnly=true) — calls
tagService.resolveEffectiveColors(doc.getTags()) which requires an open
session after the LAZY switch
- getRecentActivity: add @Transactional(readOnly=true) — callers may access
tags/receivers on the returned list; keeps session open for @BatchSize fetches
- updateDocumentTags: add @Transactional — write method was missing annotation
Also adds @JsonIgnoreProperties({"hibernateLazyInitializer","handler"}) to
Person and Tag to prevent Jackson serialization errors on uninitialized
lazy proxies.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- receivers, tags, trainingLabels: FetchType.EAGER → FetchType.LAZY
- sender: add explicit FetchType.LAZY (was implicitly lazy, now explicit)
- @NamedEntityGraph("Document.full"): sender + receivers + tags
- @NamedEntityGraph("Document.list"): sender + tags
- DocumentRepository.findById overridden with @EntityGraph("Document.full")
- DocumentRepository.findAll(Specification, Pageable) overridden with
@EntityGraph("Document.list")
- DocumentRepository.findAll(Specification) overridden with
@EntityGraph("Document.list") for RECEIVER/SENDER sort paths
- @BatchSize(50) on receivers and tags as fallback for any list path
that does not go through an @EntityGraph method
Fixes issue #467.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Hibernate statistics to the test config and two new tests in
DocumentRepositoryTest:
- findAll_withSpecAndPageable asserts ≤5 statements for 10 documents
(currently RED: EAGER @ManyToMany generates 31 secondary SELECTs)
- findById regression guard verifies collections load in ≤2 statements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- frontend/hooks.server.ts: replace request.url.includes('/api/') with
new URL(request.url).pathname.startsWith('/api/') so a page named
/my-api/something cannot accidentally match the API gate
- DomainException: add optional retryAfterSeconds field and a new
tooManyRequests() factory overload that carries the value
- LoginRateLimiter: pass windowMinutes * 60 as retryAfterSeconds when
throwing TOO_MANY_LOGIN_ATTEMPTS (RFC 6585 §4 SHOULD)
- GlobalExceptionHandler: emit Retry-After header when retryAfterSeconds
is set on a DomainException
- RateLimitInterceptor: emit Retry-After: 60 on 429 responses (1-min
window matches the existing MAX_REQUESTS_PER_MINUTE logic)
- LoginRateLimiterTest: assert retryAfterSeconds equals window duration
- RateLimitInterceptorTest: assert Retry-After header is set on 429
- JdbcSessionRevocationAdapterIntegrationTest: new @SpringBootTest +
Testcontainers test verifying revokeAll deletes all spring_session rows
and revokeOther leaves the current session intact
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove unreachable `&& !xsrfToken` condition from `handleFetch` guard;
simplify the redundant `cookieParts.length > 0` check that follows it
- Add `TOO_MANY_LOGIN_ATTEMPTS` to both Error Handling sections in CLAUDE.md
(backend and frontend) so LLMs are aware of the code without looking it up
- Add reverse-proxy IP trust and IPv6 address-cycling caveats to ADR-022
Consequences section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bucket4j-core 8.10.1 is manually pinned in pom.xml outside the Spring BOM.
Adds a packageRules entry so Renovate tracks it: patch updates auto-merge,
minor/major updates open PRs for manual review.
Addresses Tobias Concern 1 from PR #617 review.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>