For issue #380 — the new search input inside the @mention dropdown
needs an empty-state prompt distinct from "no results found".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
TranscriptionBlockController required WRITE_ALL exclusively, blocking
users with only ANNOTATE_ALL from saving, reviewing, or deleting blocks.
All write endpoints now accept {ANNOTATE_ALL, WRITE_ALL}, matching the
pattern already established in AnnotationController and CommentController.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document.list was missing receivers (caused LazyInitializationException
when sorting by receiver) and trainingLabels (latent crash for any
document with OCR training labels assigned). Document.full was missing
trainingLabels for the same reason. OSIV is disabled so every lazy
association used after the transaction closes must be in the graph.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ChronikFuerDichBox: move update() inside the failure branch so success
path skips it, matching NotificationDropdown's pattern
- NotificationDropdown test: add role=alert assertion for mark-all-read
failure to match existing dismiss-failure coverage in ChronikFuerDichBox
- +page.server.ts: use getErrorMessage(undefined) instead of null so the
missing-notificationId 400 goes through the same i18n pipeline as other errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add $state errorMessage + role=alert banner to ChronikFuerDichBox. Both enhance callbacks
now inspect result.type and set the error message on 'failure' or 'error'; errorMessage
is cleared on each new submit attempt.
Upgrade both test files to the mockFormResult pattern (via vi.hoisted) so the result
callback is exercised. Add a failing-action test in each file that asserts role=alert
appears after a form submit with type='failure'.
Fix bare Function cast → explicit typed cast to satisfy @typescript-eslint/no-unsafe-function-type.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace 'as string | null' cast (which silently accepts File values) with an explicit
typeof check. Use error: null instead of hardcoded German so the client falls through
to the generic i18n-keyed error banner.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add role="alert" to error banner so screen-reader users hear failures
- Handle result.type === 'error' (network failure) alongside 'failure' in both enhance callbacks
- Clear errorMessage at the start of each submit so stale errors don't persist on retry
- On dismiss success: skip update() entirely since goto() navigates away from the page
- On dismiss failure: await update() then set error message
- On mark-all success: skip update() (optimistic state already applied)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When dismiss-notification or mark-all-read returns a failure the dropdown
now shows a localised error message above the list. Added
notification_error_generic key (de/en/es) as the fallback when the
action response carries no explicit error string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
onClose() and goto() were firing before the server responded, making it
impossible for a fail() response to cancel navigation. Moved them inside
the result callback behind a result.type !== 'failure' guard.
Updated the $app/forms enhance mock to always invoke the returned async
callback with a configurable mockFormResult, and added three tests:
- success path calls onClose + goto with the correct deep-link URL
- failure path skips onClose and goto
- annotationId is appended to the URL when present
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Casting null to string caused PATCH to fire against /api/notifications/null/read
when the field was absent. Added an early-return fail(400) and a test that
submitting an empty form returns 400 without calling the API.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dismiss (X) button and mark-all-read button now submit forms to
/aktivitaeten?/dismiss-notification and /aktivitaeten?/mark-all-read respectively.
Props renamed onMarkRead/onMarkAllRead → optimisticMarkRead/optimisticMarkAllRead.
aktivitaeten/+page.svelte drops the now-deleted onMarkRead/onMarkAllRead wrapper functions
and passes notificationStore.optimisticMarkRead/optimisticMarkAllRead directly to the box.
Tests: $app/forms enhance mock added to both spec files so dismiss and mark-all assertions
work synchronously against form-submit events.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NotificationDropdown now wraps each row in a <form action="/aktivitaeten?/dismiss-notification">
and the mark-all control in <form action="/aktivitaeten?/mark-all-read">, wired via use:enhance
for optimistic UI. Props renamed onMarkRead/onMarkAllRead → optimisticMarkRead/optimisticMarkAllRead
to match the simplified store API. NotificationBell passes the store helpers directly; handleMarkRead
is removed.
Test mocks updated: $app/forms enhance mock fires SubmitFunction synchronously on form submit so
callback assertions work without a real HTTP round-trip.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes raw fetch() calls from the store. optimisticMarkRead(id) and
optimisticMarkAllRead() now only mutate local $state — the actual API
calls move to SvelteKit form actions on /aktivitaeten.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds two SvelteKit form actions to /aktivitaeten/+page.server.ts so the
notification bell can POST there instead of calling the backend directly
from the browser.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
trainingLabels was switched to LAZY fetch in #467 but not added to the
Document.full @NamedEntityGraph. DocumentRepository.findById() uses
Document.full to eagerly load sender/receivers/tags, but the Hibernate
session closes before Jackson serializes the response. Accessing
trainingLabels outside the session throws LazyInitializationException,
causing GET /api/documents/{id} to return HTTP 500.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
VITE_SENTRY_DSN is a Vite build-time variable baked into the JS bundle.
Without an ARG/ENV in the Dockerfile build stage and a build.args entry in
docker-compose.prod.yml, the SDK initialised with enabled=false regardless
of the Gitea secret value.
- frontend/Dockerfile: add ARG VITE_SENTRY_DSN + ENV before npm run build
- docker-compose.prod.yml: add build.args.VITE_SENTRY_DSN with empty fallback
- nightly.yml: write VITE_SENTRY_DSN secret into .env.staging
Requires Gitea secret VITE_SENTRY_DSN to be set to the GlitchTip project #1 DSN.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass SENTRY_DSN env var through to the backend container so the Sentry SDK
actually ships exceptions to GlitchTip — the variable was written to
.env.staging by nightly.yml but never forwarded into the container.
Enable Spring Boot 4.0 ECS structured logging (LOGGING_STRUCTURED_FORMAT_CONSOLE=ecs)
so Loki receives single-entry JSON log lines with parsed log.level, enabling
detected_level filtering in Grafana instead of 50-line unlinked stack trace blobs.
Update Grafana Loki dashboard query from | logfmt to | json to match the new format.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>