Comment text:
- Body and quote bumped from text-sm (14px) to text-base (16px)
to visually match the font-sans author name at text-sm
Annotation reload on delete:
- Add annotationReloadKey prop through DocumentViewer → PdfViewer
- Increment key after block delete in +page.svelte
- PdfViewer reloads annotations when key changes
- Annotation rectangle disappears immediately, not just after refresh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass activeAnnotationId to TranscriptionEditView. An $effect watches
it and sets activeBlockId to the block matching the annotation,
activating its turquoise focus border.
2 new tests (RED/GREEN):
- activates block matching activeAnnotationId (turquoise border)
- no block activated when activeAnnotationId is null
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mobile layout (< 768px):
- Split view stacks vertically: PDF top (min 40vh), blocks below
- Blocks panel gets border-top instead of border-left
- PDF remains interactive for drawing in stacked mode
Scroll-sync (block → PDF):
- Clicking a block sets activeAnnotationId
- PdfViewer effect watches activeAnnotationId, navigates to the
annotation's page if different from current, then scrolls the
annotation element into view (double-rAF for async render timing)
- Works across pages: block on page 3 navigates PDF to page 3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Own comments:
- Click the text to open inline edit (textarea replaces text)
- Enter saves, Escape cancels
- Small trash icon always visible in bottom-right corner
- Hover on text shows cursor-text + subtle bg highlight
Other people's comments: read-only, no edit/delete affordances.
Re-add currentUserId prop chain for ownership check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add blockNumbers prop through AnnotationLayer → PdfViewer → DocumentViewer.
Each turquoise annotation rectangle now shows a numbered badge (top-left,
matching the block card number in the right panel).
Block numbers are derived from sorted transcriptionBlocks, mapped by
annotationId, creating a visual link between PDF regions and block cards.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When clicking a turquoise annotation on the PDF:
- If not in transcribe mode, enters it and loads blocks
- Waits for DOM render, then scrolls to the corresponding block card
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The yellow annotation+comment system is now redundant. Transcription
blocks handle the same use case (mark region → discuss) but better,
because they also produce a transcription.
Removed:
- annotateMode state and all wiring through page/topbar/viewer/pdfviewer
- Annotate/Stop annotate buttons from DocumentTopBar
- AnnotateHintStrip import and rendering
- AnnotationSidePanel from document detail page
- canAnnotate prop from DocumentTopBar
- Color picker from PdfViewer
- Comment count badges and loadCommentCounts from PdfViewer
- Delete button from AnnotationLayer (blocks own annotation lifecycle)
- dimColor prop from AnnotationLayer
Simplified:
- AnnotationLayer: only canDraw + color + onDraw + onAnnotationClick
- PdfViewer: only draws in transcribeMode with turquoise
- Clicking annotation in transcribe mode scrolls to corresponding block
- canComment derived from canWrite (no longer needs canAnnotate)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TranscriptionBlock.svelte:
- "Kommentieren" button opens expandable comment thread per block
- Text selection in textarea captured as quoted text (> "...") prefix
- Quote hint "Text markieren für Zitat" shown when block is active/focused
- Comment thread uses existing CommentThread with blockId prop
CommentThread.svelte:
- Add blockId prop for block-level comments URL routing
- Add quotedText prop — pre-fills comment input with markdown blockquote
- commentsBase now supports 3 URL patterns: document, annotation, block
TranscriptionEditView.svelte:
- Pass canComment + currentUserId through to block components
3 new frontend tests:
- Kommentieren button present
- Quote hint shown when active
- Quote hint hidden when inactive
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AnnotationLayer: add dimColor prop — annotations matching dim color
render at 30% opacity with pointer-events disabled (300ms transition)
- PdfViewer: add transcribeMode prop, derived drawingEnabled/drawColor;
in transcribe mode draws with turquoise (#00C7B1), routes draw events
to onTranscriptionDraw callback instead of annotation endpoint
- DocumentViewer: pass through transcribeMode + onTranscriptionDraw
- Document detail page: createBlockFromDraw() POSTs to transcription
blocks API on draw completion, adds created block to list
- Mode-based dimming: yellow annotations dim in transcribe mode,
turquoise annotations dim in annotate mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace async executeSave in beforeunload handler with
navigator.sendBeacon — synchronous and reliable for page unload.
Sends pending text as JSON blob to the block update endpoint.
Fixes @Sara: "beforeunload handlers cannot reliably await async"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add turquoise/turquoise-fg semantic color tokens to layout.css
(light + dark mode), replacing all hardcoded #00C7B1 in components
- Bump Details toggle from text-xs to text-sm for visual hierarchy
- Block badge: navy → turquoise, overlapping top-left card border
with absolute positioning to visually link PDF annotation badges
- Saved indicator: smooth 300ms opacity fade before removal
(new 'fading' state in SaveState type)
- Transcribe buttons: use border-turquoise/bg-turquoise/text-turquoise-fg
Fixes @Leonie concerns: toggle visual weight, semantic tokens,
badge styling, saved fade animation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move duplicated type definition from TranscriptionEditView.svelte
and +page.svelte into $lib/types.ts for single source of truth.
Fixes @Felix: "Consider extracting the TranscriptionBlockData type"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes from PR #178 review:
Migration fixes:
- V18/V19: fix FK references from app_users to users (correct table name)
- V18: change annotation_id FK from ON DELETE CASCADE to ON DELETE RESTRICT
(block is aggregate root, cascade flows from block, not annotation)
Backend fixes:
- TranscriptionService.deleteBlock(): remove userId param, delete block first
then annotation directly via repository (no ownership check — block owns annotation)
- TranscriptionService.sanitizeText(): remove flawed regex HTML stripping,
textarea content is plain text by design — just enforce max length
- TranscriptionBlockController.requireUserId(): throw DomainException.unauthorized()
instead of silently returning null on auth failure
- CreateTranscriptionBlockDTO: add @Min/@Positive validation on coordinates
- Add @Slf4j logging to TranscriptionService for create/delete operations
Frontend fixes:
- Delete DocumentBottomPanel.svelte entirely (issue #175 requirement)
- Remove redundant mode exclusivity $effect (handled at toggle call sites)
- Remove dead handleCommentClick + onCommentClick prop (comments are future work)
- Remove quote hint UI (depends on comment feature)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DocumentMetadataDrawer: 3-column grid (≥1024px), single-column mobile
Shows document date, location, status, person cards, tag chips
Person names link to /persons/{id}, tags link to filtered search
Empty states for missing persons/tags, receiver cap with expand button
- DocumentTopBar: "Details" toggle button with animated SVG chevron
44×44px tap target, aria-expanded, Svelte slide transition
Semantic color tokens for dark mode compatibility
- Remove DocumentBottomPanel from document detail page
Bottom panel replaced by topbar drawer for metadata access
Simplify +page.server.ts (remove comments loading)
Update page.server.spec.ts for new load signature
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On small screens the upload zone now appears above recent docs.
lg:order-last keeps it visually on the right at desktop width.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restructures the dashboard to a lg:grid-cols-[1fr_300px] split:
- Left column: DashboardRecentDocuments (with stats footnote)
- Right column: DropZone (canWrite) + DashboardNeedsMetadata (flex-1)
Adds showRightColumn guard (canWrite || incompleteDocs.length > 0) so
read-only users with a complete archive never see an empty 300px ghost
column. DashboardMentions is removed from the page; the file is kept.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes /api/notifications from the dashboard widget fetches and replaces
it with /api/stats so the page no longer needs to own notification data.
Returns stats: StatsDTO | null (null on failure) instead of mentions.
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>
The password-reset E2E test was using button[type="submit"].last() to target
the password change button on the profile page. The profile page has two submit
buttons with identical text, so .last() is layout-order-dependent and breaks
if the form order ever changes.
Add data-testid="submit-password" to PasswordChangeForm and use getByTestId()
in the test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Strip malformed [[&_input]:focus:*] class fragments from PersonTypeahead
wrapper divs in both ConversationFilterBar components — PersonTypeahead
manages its own focus ring; parent selectors were redundant and broken
- Fix WhoWhenSection error state: focus:ring-red-500 → focus-visible:ring-red-500
so invalid date field ring no longer fires on mouse click
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Light: #012851 (brand-navy, 14:1 on white)
Dark: #a1dcd8 (brand-mint, 9.2:1 on canvas)
- @theme inline mapping → Tailwind ring-focus-ring utility
- Global :focus-visible fallback in @layer base
- forced-colors fallback for Windows High Contrast mode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bg-brand-mint (#A6DAD8) on text-brand-navy (#012851) = 3.5:1, fails AA
for text-xs (12px). bg-white (#fff) on text-brand-navy = 14:1 AAA.
White also reads as a distinct shape against the navy header background.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- +layout.svelte: adopt main's blue header structure (accent stripe, no
border-b, bg-header instead of bg-brand-navy)
- layout.css light mode: drop --c-nav-active (removed by main); set
--c-header: #012851 (confirmed correct now that header is brand-navy)
- layout.css dark mode: drop --c-nav-active; keep navy PDF tokens and
--c-header: #012851 from our branch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change header --c-header dark value from #01335e to #012851 (brand navy):
#01335e gave 4.3:1 with ink-3 (WCAG AA fail); #012851 gives 4.99:1 (pass)
- Switch header element from bg-surface to bg-header so dark mode uses the
independent --c-header token instead of inheriting the surface background
- Fix both dark blocks (media query and manual override) to stay in sync
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace neutral dark tokens (#0d0d0d, #1a1a1a, etc.) with navy-tinted
values derived from brand-navy: canvas #010e1e, surface #011526,
overlay #011e38, muted #011a30
- Fix --c-ink-3 WCAG AA failure in [data-theme='dark'] block:
#6b7280 (3.2:1, fail) → #8b97a5 (7.1:1, AAA ✓)
- Add color-scheme: dark to both dark blocks for native OS scrollbar theming
- Update PDF viewer tokens to navy palette (bg #010e1e, ctrl #011526, text #f0efe9)
- Add --c-header token (#ffffff light / #01335e dark) for independent
header surface control; mapped to --color-header in @theme inline
- Fix EntityNav contrast: text-white/30 → /50 (heading) and text-white/20
→ /50 (inactive count badges) to pass WCAG AA 4.5:1 on bg-brand-navy
Closes#166
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Normalize all header icon buttons to white/65 + white/10 hover bg
- Fix guest person icon (img tag needs brightness-0 invert, not text color)
- Add missing focus-visible rings to ThemeToggle and LanguageSwitcher
- Use focus-visible:rounded on nav links so active underline stays sharp
- Bump burger/nav breakpoint from sm→lg to prevent overflow on tablets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the absolutely-positioned language switcher div and replaces it
with the shared AuthHeader component (logo + lang switcher on navy bar).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Provides logo + language switcher on brand-navy background with
4px accent strip. Used on login and forgot-password pages in place
of the floating language switcher.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace bg-surface border-b with bg-brand-navy (always #012851)
- Add 4px bg-accent strip above the nav bar
- Remove border-r separator from language switcher wrapper
- Pass inverted prop to LanguageSwitcher for white text on dark bg
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The nav active state moves from a background pill to a bottom-border
underline, so the rgba purple tint variable is no longer needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaced static brand-sand/brand-mint/brand-navy tokens with themed
semantic tokens (bg-accent-bg, border-accent, text-ink) so the hint bar
adapts correctly in dark mode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blockers (14):
- B1: fix senderName/receiverName to use $derived instead of $state + sync $effect
- B2: migrate all korrespondenz components from messages-extra shim to paraglide m.*
- B3: i18n CorrespondenzEmptyState (heading, subtext, search placeholder)
- B4: add response.ok checks to admin layout server load
- B5: add response.ok checks to korrespondenz page server load
- B6: add page.server.spec.ts with 5 test suites for korrespondenz load function
- B7: add axe-core accessibility checks to all e2e korrespondenz tests
- B8: add Testcontainers JPQL tests for findSinglePersonCorrespondence (DISTINCT + sender)
- B9: hide auth reset-token endpoint from OpenAPI spec; remove from generated api.ts
- B11: replace amber hardcoded hex colors in SinglePersonHintBar with brand tokens
- B12: replace clipboard emoji with Heroicons SVG in SinglePersonHintBar
- B13: create DateInput component (German dd.mm.yyyy); use it in CorrespondenzFilterControls
- B14: add Paraglide compile step to CI workflow before lint/test
Suggestions (11):
- S1: make CorrespondentSuggestionsDropdown a pure display component; lift fetch to PersonBar
- S2: fix leftover messages-extra import in ConversationTimeline; use brand tokens for status dots
- S3: add intent comment to EntityNav openFlyout behavior
- S4: rename canManageGroups → canManagePermissions throughout admin
- S6: remove domFlush helper from DateInput spec; use expect.poll instead
- S7: replace test.skip with throw new Error in bilateral e2e tests
- S8: add inverse aria-disabled test for filter strip
- S9: remove sm:min-h-0 from sort button to preserve 44px touch target
- S10: add title attributes to tablet trigger buttons in EntityNav
- S11: delete messages-extra.ts shim entirely
Also: fix admin pages revealing blank strip at bottom (-mb-6 on admin layout)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap strips in -mt-6 to negate main's py-6 top padding; strips now flush at top
- Year divider: text-2xl font-black for the year number (was text-[15px])
- Year count and all log row meta text: text-sm minimum (was text-xs)
- Asymmetry bar counts: text-sm (was text-[10px])
- No-results box: replace hardcoded hex with theme tokens
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>