Adds color field assigned from an 8-colour palette keyed on the user's UUID
hash (Math.abs(id.hashCode()) % 8). Fires via @PrePersist/@PreUpdate/@PostLoad
so both new and existing users get the correct colour at runtime.
V47 migration adds the column and fixes the V46 REVOKE bug that hardcoded
role name 'app_user' instead of CURRENT_USER.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instruments CommentService.postComment(), postBlockComment(), and
replyToComment() to fire COMMENT_ADDED after each successful save and
MENTION_CREATED once per mentioned user. The shared logCommentPosted()
helper avoids duplicating the two-call pattern across all three post
methods.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract logAfterCommit() from AnnotationService and TranscriptionService
into AuditService, eliminating duplicate boilerplate (Markus)
- Remove UserService from DocumentService; add actorId param to
storeDocument(), attachFile(), updateDocument() instead — resolves
SecurityContextHolder coupling concern (Markus)
- Update DocumentController to inject UserService and resolve actorId
from Authentication, passing it through to service methods
- Add logAfterCommit() tests to AuditServiceTest with MockedStatic
- Update all test verify() calls to use logAfterCommit() (not log())
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- reviewBlock: add userId param; log BLOCK_REVIEWED only on false→true
- updateBlock: log TEXT_SAVED only when text actually changes; include
pageNumber in payload (resolved from annotation)
- Both events deferred via afterCommit() when inside a transaction
- Update TranscriptionBlockController to pass user to reviewBlock()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Visiting /register without a code now shows a friendly error card
explaining the archive is invite-only, instead of the empty form.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the minimal login-style form with the full spec design:
hero section (eyebrow, headline, subtext), three labelled form sections,
2-column name grid, confirm-password field with client-side match hints,
password strength indicator, notification checkbox card, loading state on
submit, and "already have an account?" footer link.
Backend: adds notifyOnMention to RegisterRequest and wires both
notifyOnMention and notifyOnReply via updateNotificationPreferences on
invite redemption.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Narrow isTrustedProxy to RFC 1918 172.16-31.x.x (was 172.x.x.x)
- Add @Valid/@NotBlank/@Email to RegisterRequest and @Valid to AuthController
- Add FK constraint on invite_token_group_ids.group_id → user_groups(id)
- Add back-to-login link and <main> landmark to register error state
- Add component test suite for register/+page.svelte (11 tests)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WCAG 1.3.1: add for/id pairs to all 6 fields in the create-invite form
- WCAG 1.4.1: add status icon (●○✕⏱) to status badge alongside label
- Add aria-label to copy-link buttons in the invite table
- Replace hardcoded German strings with i18n keys (Alle, Widerrufen,
Link kopieren, Kopiert, Abbrechen)
- Increase filter button touch targets py-1.5 → py-2
- Add 5 unit tests for register page load function (no-code, ok,
error-with-code, error-without-code, URL-encoding)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
InviteService was directly injecting AppUserRepository, UserGroupRepository,
and PasswordEncoder — crossing domain boundaries that UserService owns.
- Add UserService.createUser() with duplicate-email guard
- Add UserService.findGroupsByIds() delegation method
- InviteService now only injects UserService (not user repositories)
- generateCode() now throws INTERNAL_ERROR after 10 failed attempts
instead of looping indefinitely
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without this guard any client could send X-Forwarded-For: <spoofed-ip>
and bypass per-IP rate limiting entirely.
Also switches expireAfterWrite → expireAfterAccess so the 1-minute
window starts at first request, not last, and fixes the .gitignore
entry that accidentally merged **/test-results/ and .worktrees/ into
one broken pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add @Email annotation to CreateUserRequest.email and AppUser.email
- Add @Valid to UserController.createUser to activate bean validation
- Add MigrationIntegrationTest cases for V44 NOT NULL and UNIQUE constraints
- Fix stale test comments (findByUsername → findByEmail)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously a blank email string would silently set email to null,
which would cause a DB constraint violation after V44 migration.
Now throws DomainException.badRequest instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
loadUserByUsername now calls findByEmail and returns email as the
Spring Security principal name. Tests updated to assert email identity.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add * to Datum and Absender labels (both are required fields)
- Add required prop to PersonTypeahead to show * in its label
- Move "Optional" divider in DescriptionSection to after Titel (the only
required field), so Tags and Inhalt appear below the divider where they belong
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers: button present, confirm dialog opens, form submitted on confirm,
form not submitted on cancel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds label_required_fields to all three locales. Fixes "Datei ersetzen"
toolbar colors to use semantic ink tokens (readable in both light and dark
pdf-bg themes).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract DocumentEditLayout shared component for the PDF+form split-panel
UI, replacing the old scrolling layout on /documents/[id]/edit with the
same fixed-panel structure used by /enrich/[id]. Removes TranscriptionSection
and FileSectionEdit from the edit page; file upload/replace is now handled
by the shared layout. Delete SaveBar and FileSectionEdit as dead code.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- error_file_upload_failed key used in enrich upload handler
- label_optional key added (de/en/es) and used in DescriptionSection divider
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- text-[9px]/text-[10px] in required-fields bar raised to text-xs (12px),
meeting the project minimum for the 60+ audience (WCAG 1.4.4)
- Upload animation now uses motion-safe: prefix so it stops for users
with prefers-reduced-motion set (WCAG 2.1 SC 2.3.3)
- Strengthened UploadZone tests: onCancel uses [role=status] button
selector instead of first-button heuristic; added positive file
selection test (valid PDF calls onFile), file-too-large test, and
MIME rejection now also asserts the error message is visible
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DocumentService.attachFile() now catches IOException internally and
re-throws as DomainException.internal — the IOException no longer leaks
through the service boundary
- DocumentController.attachFile() is now a plain delegate (no try/catch)
- ALLOWED_CONTENT_TYPES whitelist (PDF/JPEG/PNG/TIFF) is now enforced on
the attachFile endpoint, matching the existing quick-upload validation
- Added 5 DocumentService unit tests for attachFile (notFound, status
transition PLACEHOLDER→UPLOADED, no-change when already UPLOADED,
field assignment from upload result, IOException→DomainException)
- Added controller tests: 400 on disallowed content type, 404 on missing doc
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Required-fields progress bar (Pflichtfelder) with role="progressbar" ARIA tracks
Titel, Datum, and Absender live via bound props from child components
- Left panel shows UploadZone for PLACEHOLDER documents (no filePath); after upload
invalidates 'app:document' to transition to PDF viewer without page reload
- AbortController powers the cancel button during upload
- "Datei ersetzen" ghost button lives in a thin toolbar above the PDF viewer
- dateIso and currentTitle are now bound from WhoWhenSection/DescriptionSection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Field order: Titel → Schlagworte → Kurzinhalt → [Optional divider] → Aufbewahrungsort.
currentTitle is now bindable so the enrich page can derive the required-fields progress bar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Required fields (Datum, Absender) move to row 1; optional fields (Empfänger, Ort)
to row 2. dateIso is now bindable for the progress bar. Autofocus lands on the
first empty required field on page load.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pixel-accurate spec for the dashboard redesign: Resume + Family Pulse
layout with hero resume card, mission control 3-up, and activity feed.
Relates to #271
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Captures the centered-card registration design 1:1 from the claude.ai/design export. Covers all 10 sections: desktop overview, header, above-card copy, form fields, password states, notification card, submit button, success panel, mobile layout, and i18n/a11y/backend implementation notes.
Relates to #269
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add missing test coverage for the amber QUEUED status badge in TrainingHistory.
Fix WCAG 2.2 minimum touch target (24 × 24 px) on the success-message dismiss
button in OcrTrainingCard. Add focus-visible ring to the expand/collapse toggle
in TrainingHistory so keyboard users get a visible focus indicator.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>