- Add displayName default method to PersonSummaryDTO
- Update native SQL queries to include title, person_type columns
- Add getInitials() utility to personFormat.ts
- Update abbreviateName/abbreviateCompact for nullable firstName
- Replace firstName+lastName concatenation with displayName in all
person-displaying components and server load files
- Regenerate API types with displayName on Person and PersonSummaryDTO
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Six categories of breakage:
1. date.ts — add formatGermanDateInput(raw: string): string as a pure
function covering both digit-stream auto-dot and manual-dot-with-padding
modes. Refactor handleGermanDateInput to delegate to it. Fixes 16 failures
in date.spec.ts where the function was imported but didn't exist.
2. Admin layout specs (groups/tags/users) — $effect fires on initial mount
with manualCollapse=false, so the spy captured 'false' before the click's
effect ran. Fix: move spy setup after render(), add await setTimeout(0) to
flush Svelte effects before asserting.
3. DashboardMentions — component now renders a persistent
"Benachrichtigungsverlauf ansehen" link, making getByRole('link') strict-
mode violations. Fix: scope link queries to the actor name, and check
absence of the actor link (not all links) in the no-documentId test.
4. Conversations page — empty-state copy changed from "Wählen Sie zwei
Personen aus" to "Korrespondenz durchsuchen". Update the test.
5. Login page — AuthHeader adds a second aria-label="Familienarchiv" link.
Use .first() to avoid strict-mode violation.
6. Persons page — alias is rendered with German quotation marks „…" not
straight quotes "…". Update the test string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolved conflicts in messages/de.json, en.json, es.json by keeping
both the persons-redesign keys (feature branch) and the notification
keys (main) in all three locale files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unit tests for both; i18n keys for doc status and person stats bar;
PERSON_NOT_FOUND added to frontend ErrorCode type.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracted from NotificationBell.svelte into $lib/utils/notifications.ts so the
history page can reuse them. relativeTime() now accepts an optional `now` param
for deterministic unit testing. Added parseNotificationEvent() for SSE payload
shape validation (NullX Finding 3).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AnnotationSidePanel: cover visibility (null vs set annotationId),
close button callback, and targetCommentId forwarding
- layout.svelte.spec: mock $env/static/public to satisfy
PUBLIC_NOTIFICATION_POLL_MS import from NotificationBell
- mention.spec: update assertion to match span-based mention rendering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BLOCKERs:
- Remove direct AppUserRepository/CommentRepository access from CommentService and
NotificationService — replaced with UserService.findAllById() and UserService
(fixes layering contract from CLAUDE.md)
- Switch Optional<JavaMailSender> constructor injection — removes @Autowired(required=false)
field and ReflectionTestUtils hack in tests
- Add @RequirePermission(READ_ALL) to UserSearchController — prevents user enumeration
without read access
Data bug:
- Promote actorName from @Transient to persisted VARCHAR column (V18 migration)
- Set actorName in notifyReply and notifyMentions from comment.getAuthorName()
Architecture:
- Add @RequirePermission(READ_ALL) to NotificationController
- Introduce NotificationDTO — controller returns DTO instead of Notification entity,
eliminating lazy-load N+1 and AppUser field leakage
- Change mentions FetchType to EAGER — fixes LazyInitializationException outside transaction
- Add @Transactional(propagation=REQUIRES_NEW) to notifyReply/notifyMentions so a
notification failure cannot roll back the parent comment
- N+1 fix: replace per-ID findById loops with single findAllById bulk fetch
- Move collectParticipantIds to CommentService; notifyReply accepts Set<UUID> directly
Security:
- Escape displayName before injecting into renderBody HTML span
- Replace <a href="#"> with <span class="mention"> — no profile page to link to, and
the anchor's scroll-to-top behaviour is harmful
Tests added/fixed:
- markRead_throwsNotFound, markAllRead_delegatesToRepository, countUnread_delegatesToRepository
- markOneRead_returns401, @RequirePermission 403 coverage for both controllers
- postComment/replyToComment_triggersNotifyMentions_whenMentionedUserIdsProvided
- search_returnsAtMostTenResults now asserts $.length() <= 10
- XSS regression test for escaped displayName in mention.spec.ts
Frontend minors:
- relativeTime() uses Intl.RelativeTimeFormat (locale-aware, not German-hardcoded)
- aria-label uses m.notification_unread() Paraglide key (de/en/es added)
- <div role="button"> replaced with <button> (native Enter+Space handling)
- onDestroy clears debounceTimer in MentionEditor
- setTimeout(100) replaced with await tick() + requestAnimationFrame in CommentThread
- Notification prefs form uses checkbox name attributes + formData.has() pattern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the four fixed regexes with a split-based algorithm:
- first segment = date → last segment = firstName, rest = lastName parts
- last segment = date → second-to-last = firstName, rest = lastName parts
18881025_de_Gruyter_Walter.pdf now correctly yields "Walter de Gruyter".
Simple two-segment names behave identically to before.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Supports four patterns: date_lastname_firstname and lastname_firstname_date,
both with ISO (YYYY-MM-DD) and compact (YYYYMMDD) date formats.
Returns dateIso, personName and a formatted suggestedTitle.
Partial matches are rejected — unrecognised filenames return {}.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move isoToGerman and germanToIso from utils.ts into utils/date.ts alongside
formatDate, and add handleGermanDateInput for the shared date field handler.
Make utils.ts a re-export shim so existing imports continue to work.
Closes part of #75
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Pre-commit hook
- Add .husky/pre-commit at repo root: runs `cd frontend && npm run lint`
- Update prepare script in package.json to auto-configure git hooks path
on npm install (git -C .. config core.hooksPath .husky)
- Add lint step to CI unit-tests job so it catches issues before tests run
- Add generated dirs to .prettierignore (paraglide_bak*, test-results, .auth)
- Add src/lib/paraglide_bak* to .gitignore so ESLint can ignore them
## ESLint fixes (all pre-existing)
- Disable svelte/no-navigation-without-resolve: false positive in SvelteKit
(rule targets Svelte 5 standalone routing, not SvelteKit <a href>)
- Fix svelte/require-each-key: add (item.id)/(item) keys to all {#each} blocks
across 10 files — improves Svelte reconciliation performance
- Fix svelte/prefer-writable-derived in PersonTypeahead: $state+$effect → $derived
- Fix svelte/prefer-svelte-reactivity: URLSearchParams → SvelteURLSearchParams,
Map → SvelteMap (enables Svelte reactive tracking)
- Fix @typescript-eslint/no-unused-vars: remove dead imports/variables
## Prettier
- Run npm run format to bring all source files in line with .prettierrc
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Admin page: replace 7 identical error-handling blocks with a single
toActionResult() helper — DRY without over-abstraction
- New date.ts util: formatDate(isoDate) centralises the T12:00:00
timezone guard and Intl.DateTimeFormat locale config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracted sortDocumentsByDate utility with full Vitest coverage (6 tests),
wired it into the person detail page with a DESC/ASC toggle button, and
added an E2E smoke test for the toggle interaction.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>