Commit Graph

462 Commits

Author SHA1 Message Date
Marcel
c1b221412f fix(bulk-upload): PDF-only file acceptance
Drop non-PDF accept types from file input and update format hint strings
in all three languages. JPEG/PNG/TIFF were never officially supported.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:22 +02:00
Marcel
76c14ea604 fix(bulk-upload): form layout polish and drop zone sizing
- Drop zone box doubled: max-w-xl, larger icon (80px), bigger padding and text
- Title field wrapped in its own card (matches WhoWhenSection/DescriptionSection)
- Removed double-wrapping outer card around WhoWhenSection + DescriptionSection
- Added space-y-4 between form sections for consistent breathing room
- ScopeCard per-file label: text-accent → text-primary for legible contrast in light theme

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:22 +02:00
Marcel
539842e849 fix(bulk-upload): spec-compliant split-panel layout with local PDF preview
Rewrites BulkDocumentEditLayout to match the spec exactly:
- Fixed viewport layout (same as DocumentEditLayout) filling viewport below nav
- Split panel visible in all states (N=0/1/≥2) — was fullscreen dark drop zone
- N=0: centered drop-zone-box in left panel; shared form visible but greyed out
- N≥1: real PDF preview via URL.createObjectURL (no server upload required)
- N≥2: FileSwitcherStrip at bottom of left panel; count pill + discard in topbar
- FileEntry gains previewUrl; blob URLs created on add, revoked on remove/destroy
- save() checks response.ok and marks failed files with status: 'error'
- BulkDropZone redesigned: spec-accurate box with circular mint icon, serif title
- FileSwitcherStrip: number badges, arrows, keyboard nav via data-chip-id selector
- ScopeCard, UploadSaveBar: hardcoded German replaced with Paraglide i18n keys
- +page.svelte simplified to bare component render (layout is self-contained)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:22 +02:00
Marcel
801470093d feat(bulk-upload): add BulkDocumentEditLayout component with save handler
State-owner for the bulk upload flow:
- N=0: full-panel BulkDropZone
- N=1: title + shared metadata (no switcher/scope cards)
- N≥2: FileSwitcherStrip + per-file ScopeCard + shared ScopeCard
Save handler chunks files at 10/request, POSTs to /api/documents/quick-upload
with typed metadata JSON part, tracks progress, redirects to /documents.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:22 +02:00
Marcel
af6ba6a9cc feat(bulk-upload): add UploadSaveBar component + fix bulk_save_cta message
Save bar with sticky positioning, a determinate progress bar while
uploading chunks, plural save CTA, and a destructive discard link.
Replaces broken ICU plural in bulk_save_cta with two-key approach
(bulk_save_cta_one / bulk_save_cta) since Paraglide 2.5 does not support
ICU plural syntax.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:22 +02:00
Marcel
9acd5ec617 feat(bulk-upload): add ScopeCard component
Card container with two variants: per-file (mint tint) and shared (neutral
with file-count badge). Used to visually separate per-file vs shared
metadata sections in the bulk upload layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:22 +02:00
Marcel
29a44b3cd1 feat(bulk-upload): add FileSwitcherStrip component
Horizontal chip strip for switching between files in a bulk upload session.
Supports keyboard navigation (arrow keys cycle within the strip), error state
chips, and onSelect/onRemove callbacks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:22 +02:00
Marcel
5fe289b06b feat(bulk-upload): add BulkDropZone component
Full-panel drop target that supports multi-file selection via drag-and-drop
or file picker. Fires onFilesAdded callback with the full File array.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:22 +02:00
Marcel
48d034dcb8 fix(transcribe-coach): propagate hover from 44px button group to inner span
Some checks failed
CI / Backend Unit Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
hover: on the <span> only fired on the 20×20px visual circle, not the
full 44×44px touch target. Add `group` + `focus-visible:ring-*` to the
outer button; switch to `group-hover:` on the inner span so the visual
response covers the entire interactive area.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:02 +02:00
Marcel
5b7c37391c test(HelpPopover): use userEvent.keyboard for Enter/Space tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:02 +02:00
Marcel
077f5c85df fix(TranscribeCoachEmptyState): Tailwind grid instead of inline styles; step aria-labels
- Replace style="grid-template-columns: 34px 1fr; align-items: start;"
  with Tailwind grid-cols-[34px_1fr] items-start (Felix: inline styles)
- Add aria-label="Schritt N von 3" on each <li> so screen readers announce
  step position when the numeric badge is aria-hidden (Nora/Sara)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:02 +02:00
Marcel
018e272a3b fix(RichtlinienRuleCard): bg-[#FAF8F1] → bg-parchment design token
Raw hex bypassed the token system and wouldn't remap in dark mode.
Now uses --color-parchment which has a proper dark-mode counterpart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:02 +02:00
Marcel
0c4a0ead7b fix(TranscribeDragDemo): reactive prefersReducedMotion + bg-parchment token
- Replace one-shot $derived(.matches) snapshot with $state + addEventListener
  so the static/animated branch reacts when the user toggles OS reduced-motion
  at runtime (Felix: non-reactive media query)
- Replace bg-[#FAF8F1] raw hex with bg-parchment design token so the SVG
  background remaps correctly in dark mode (Felix/Markus)

Also update TranscriptionPanelHeader.svelte.test.ts to expect role="region"
after the HelpPopover ARIA fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:02 +02:00
Marcel
82b12d4383 fix(HelpPopover): role=region, 44px touch target, counter-based ID
- role="tooltip" → role="region" + aria-label={label}: tooltip semantics
  are wrong for a click-triggered panel (Nora/Sara)
- expand button to 44×44px with inner visual <span>: WCAG 2.5.8 touch
  target for 60+ transcriber audience (Sara/Leonie)
- replace Math.random() with module-level counter: SSR/hydration mismatch
  when server and client generate different IDs (Felix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 12:24:02 +02:00
Marcel
b234db0472 feat(richtlinien): add /hilfe/transkription page with RichtlinienRuleCard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:28:25 +02:00
Marcel
7c3a8e7651 feat(transcribe): add HelpPopover primitive and wire (?) chip into panel header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:19:48 +02:00
Marcel
dff203d526 feat(transcribe): wire coach into TranscriptionEditView, hide training footer when empty
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:06:50 +02:00
Marcel
86584a53a8 feat(transcribe): add TranscribeCoachEmptyState and TranscribeDragDemo components
New coach card replaces the icon+sentence empty state in the Transcribe
panel (edit mode). Three-step guide with 5-s SMIL drawing animation in
step 1 only. Animation freezes at the final frame when
prefers-reduced-motion is active. Footer links to Wikipedia Kurrent and
the Richtlinien page open in new tabs with visible '(öffnet in neuem Tab)'
annotations. 34 new i18n keys in de/en/es.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:00:01 +02:00
Marcel
6e021fb23a fix(briefwechsel): repair 500 by consuming backend thumbnailUrl directly
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m46s
CI / OCR Service Tests (push) Successful in 29s
CI / Backend Unit Tests (push) Failing after 2m56s
ConversationThumbnail still imported the `$lib/thumbnails` helper that
a02f6cdc deleted, so every SSR render of /briefwechsel crashed with
"Cannot find module '$lib/thumbnails'". Finish that refactor by reading
`doc.thumbnailUrl` straight off the Document DTO (same shape
DocumentThumbnail already uses), and update the spec fixtures to match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 13:27:19 +02:00
Marcel
18b88672ec fix(pagination): bound controls render as aria-hidden spans — address @leonievoss
<a aria-disabled="true"> is the documented pattern but screen readers
still announce "Previous, link, disabled" on pagination bounds — noise
users don't need because the disabled state is purely visual. Switching
to <span aria-hidden="true"> removes the bound control from the AT tree
entirely (Leonie's recommendation). Visual parity preserved via a
disabledBase Tailwind class (same layout + cursor-not-allowed + opacity-40).

Tests updated: "disabled prev/next" assertions now check for aria-hidden
and no href — the active-state href/aria-current assertions are
unchanged. (#316)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 13:20:24 +02:00
Marcel
78ac5d663d feat(documents): paginate search with a Pagination control
Frontend side of the /documents pagination work. The page.server.ts load
reads ?page= from the URL, forwards page+size=50 to the backend, and
exposes the new totalElements/pageNumber/pageSize/totalPages fields on
`data`. +page.svelte renders a <Pagination> component below the result
list; buildPageHref preserves every filter param and only updates page.
The existing triggerSearch debounce flow intentionally drops `page`
when any filter changes, so filter edits reset to page 0 automatically.

<Pagination> uses plain <a href> links (not goto) so SvelteKit's default
scroll restoration scrolls new pages to the top — the expected senior-UX
behaviour. Decorative chevrons wrapped in aria-hidden spans, 44px touch
targets, focus-visible ring, stacks vertically under 640px. The control
hides itself when totalPages ≤ 1.

Test coverage: 9 cases on Pagination (label, aria-current, prev/next
enable/disable, makeHref invocation, decorative chevron, touch target),
plus a filter-reset assertion on +page.svelte (page 5 → edit q →
goto URL must drop page=). Adds i18n keys in de/en/es. Manual edit to
api.ts pending a post-merge npm run generate:api against a rebuilt
dev backend. (#315)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 13:20:24 +02:00
Marcel
7007491d8c style(dashboard): address @leonievoss — scale fallback icon to match larger container
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 3m26s
CI / OCR Service Tests (pull_request) Successful in 35s
CI / Backend Unit Tests (pull_request) Failing after 3m4s
CI / Unit & Component Tests (push) Failing after 3m7s
CI / OCR Service Tests (push) Successful in 36s
CI / Backend Unit Tests (push) Failing after 3m17s
h-16 w-16 looked undersized in the 180×252 strip container (~25% of
the height). h-24 w-24 gives ~38% visual weight, matching the ratio
DocumentThumbnail uses for its lg (120×168) fallback (#309).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 07:26:23 +02:00
Marcel
72cd6f5bbc feat(dashboard): fall back to document-text heroicon when no thumbnail yet
Uses the same heroicon as DocumentThumbnail so the "no thumbnail yet"
signal reads identically across the app: one shape, one meaning. The
parchment SVG still lives on in the fully-empty state (no resume doc
at all), where it represents a different thing — we removed it only
from the "document exists, thumbnail not generated yet" branch (#309).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 07:26:23 +02:00
Marcel
1d44bbb1bd feat(dashboard): render real document thumbnail in resume strip
Replaces the generic parchment SVG placeholder with an <img> pointing at
the backend's thumbnail endpoint when the document has one. The 180×252
container matches DocumentThumbnail's 5:7 A4 convention so the
dashboard tile sits visually next to the list/person-sublist tiles
instead of looking squatter than they do. dark:mix-blend-multiply keeps
paper scans from glaring on a dark page background (#309).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 07:26:23 +02:00
Marcel
817749889a refactor(document-thumbnail): read doc.thumbnailUrl instead of composing locally
The backend now exposes thumbnailUrl as a serialised computed property
on Document, so the component drops its dependency on the frontend
URL-builder. PersonDocumentList's inline Doc prop type follows the
same shift (#309).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 07:26:23 +02:00
Marcel
0b33f323ee feat(briefwechsel): restore direction arrow next to row title
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m45s
CI / OCR Service Tests (push) Successful in 31s
CI / Backend Unit Tests (push) Failing after 2m56s
PR #311 dropped the right/left arrow icons that signalled whether a
letter was sent or received. Readers who don't decode the colored
left border (new users, color-blind users, users at a glance) had
no visual cue for direction. Restore a 20×20 arrow inline with the
title — right-arrow for outgoing, left-arrow for incoming — kept
decorative (aria-hidden) since the aria-label already announces
"Gesendet:" / "Empfangen:".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 21:57:57 +02:00
Marcel
334b624063 feat(briefwechsel): bump row typography and drop relative-year chip
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m50s
CI / OCR Service Tests (push) Successful in 34s
CI / Backend Unit Tests (push) Has been cancelled
The 168px-tall thumbnail tile was dominating rows where the text
column only rendered at text-xs / text-sm — visually the right
column sat half-empty. Three changes:

- Title: text-sm → text-lg
- Summary: text-sm → text-base
- Meta + tag chips: text-xs → text-sm

And remove the "vor N Jahren" chip entirely. The documentDate
in the meta row already carries the temporal context and the
chip was adding visual noise without new information.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 21:52:23 +02:00
Marcel
503ce49ef7 refactor(briefwechsel): TagChipList defaults max to 3
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m51s
CI / OCR Service Tests (push) Successful in 30s
CI / Backend Unit Tests (push) Failing after 2m56s
Makes `max` an optional prop with default 3 — the common row-layout
case doesn't need to name the cap explicitly. ThumbnailRow's callsite
drops to `<TagChipList tags={doc.tags ?? []} />`, consistent with how
other shared components in $lib/components expose sensible defaults.

Refs #305
Fixes @leonievoss round-2 follow-up from PR review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
f5a30c71b7 i18n(briefwechsel): ThumbnailRow direction label via Paraglide
Adds row_direction_sent / row_direction_received keys across the
three locale files (de: Gesendet/Empfangen, en: Sent/Received, es:
Enviada/Recibida) and routes ThumbnailRow's directionLabel through
Paraglide. An English or Spanish screen-reader user now hears
"Sent:" / "Enviada:" in their language, matching the DistributionBar
i18n pass.

Refs #305
Fixes @leonievoss round-2 follow-up from PR review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
a7ab5e6e69 refactor(briefwechsel): extract TagChipList from ThumbnailRow
Lifts the three-chip-plus-"+N" tag row out of ThumbnailRow into a
standalone TagChipList component so the chip cap + overflow policy
lives in one place and can be reused on other surfaces (document
detail header is a candidate). ThumbnailRow drops from 110 to ~90
lines and no longer owns tag-slicing logic — it just asks for the
list with max=3.

Behavior is byte-identical: same data-testid, same max cap, same
"+N" overflow indicator. All ThumbnailRow row-level tag tests
continue to pass against the new composition.

Refs #305
Fixes @felixbrandt suggestion 1 from PR review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
9ecf7f4dfc refactor(briefwechsel): ThumbnailRow captures now at prop binding
Defaults `now` in $props() destructure so each row instance freezes
its reference time at mount, instead of calling new Date() inside
the $derived every reactivity tick. No behavioural change — the
date math is stable across re-renders for a given row — but drops
the nullish-coalesce dance and is cleaner under Storybook-style
testing where a deterministic `now` is injected.

Refs #305
Fixes @felixbrandt suggestion 3 from PR review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
01bfc59849 test(briefwechsel): lock future-date relative-year hiding at the row layer
relativeYearsDe already returns "" for future dates (covered in its
own spec), but the integration wiring inside ThumbnailRow was
untested. Adds a regression that a doc with documentDate in the
future produces no "vor N Jahren" or "vor weniger als 1 Jahr" chip.

Refs #305
Fixes @saraholt concern 5 from PR review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
7090f9a0e0 feat(briefwechsel): ConversationThumbnail page badge legible at small sizes
Bumps the multi-page badge from text-xs (12px) / px-1.5 py-0.5 to
text-sm (14px) / px-2 py-1. Meets senior-legibility on a 320px phone
without crowding the 120-wide tile — the badge stays tucked in the
top-right corner.

Refs #305
Fixes @leonievoss senior-accessibility concern from PR review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
d4617a96d1 i18n(briefwechsel): DistributionBar reads text + aria-label via Paraglide
Drops the hardcoded German strings ("Briefverteilung in diesem Zeitraum",
"{n} von {name}") and routes every visible + assistive-tech string
through dist_bar_aria and dist_bar_segment message keys. An English
or Spanish user now sees "from" / "de" instead of "von" both on
screen and in the aria-label their screen reader announces.

Refs #305
Fixes @leonievoss i18n concern from PR review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
b9dda9a938 feat(briefwechsel): ThumbnailRow aria-label leads with Gesendet/Empfangen
Without this prefix, a color-blind user or screen-reader user has no
indication of correspondence direction — the colored left border is
information but not announced, and the arrow glyphs were removed in
the earlier layout pass. Prepending "Gesendet:" or "Empfangen:" to
the aria-label gives assistive-tech users the direction first so the
row identity is unambiguous even without color perception.

Refs #305
Fixes @leonievoss WCAG 1.4.1 concern from PR review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
cc118ffb16 feat(briefwechsel): add ThumbnailRow for the new correspondence row layout
Combines ConversationThumbnail with a quote-styled summary, truncated
meta line, and up to three tag chips (the rest collapsed into "+N").
The colored left border tells a reader at a glance whether this
letter left or entered the perspective person's mailbox — replacing
the previous status dot + script-type icons that were too busy for
the list view. Relative-year label ("vor 76 Jahren") is derived from
documentDate so the list carries temporal context without a full
date column.

Rendering rules:
- title falls back to originalFilename when empty
- summary uses a text expression, never {@html}, so inline markup
  in the summary field is escaped (XSS regression test locks this)
- focus-visible outline + focus-within hover keep keyboard-only
  users in sync with mouse hover feedback
- aria-label always pairs title with the formatted date so screen
  readers hear both identifiers

Refs #305

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
407bfbd5f1 feat(briefwechsel): add ConversationThumbnail with aspect + page badge
Reads thumbnailAspect from the backend and swaps between a 120×168
portrait tile and a 168×120 landscape tile so postcards and photos
don't get cropped into a portrait frame. Shows a page-count badge
top-right for multi-page PDFs, and a pulsing skeleton while the
async thumbnail job hasn't run yet. URL assembly goes through the
existing thumbnailUrl helper so cache-busting stays consistent
with DocumentThumbnail.

Refs #305

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
d99f4544d2 refactor(briefwechsel): extract bilateral DistributionBar component
Lifts the inline distribution bar out of ConversationTimeline so the
same two-tone ratio widget can be reused on other bilateral surfaces
(e.g. the person detail page). Markup/styling is byte-identical to
the inline version; only the prop interface is new.

Refs #305

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 21:38:56 +02:00
Marcel
90f111fcb1 style(documents): bump right-column font-size from xs to sm in list rows
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m32s
CI / OCR Service Tests (push) Successful in 30s
CI / Backend Unit Tests (push) Failing after 2m50s
The 12px text felt cramped next to the larger 120×168 thumbnail. Lift
the date / VON / AN / progress label to 14px so the row reads
comfortably without changing the width or the row height.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 09:17:30 +02:00
Marcel
bca27898f7 fix(documents): tag click no longer navigates to document detail page
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m38s
CI / OCR Service Tests (push) Successful in 29s
CI / Backend Unit Tests (push) Failing after 2m55s
Nesting the tag <button> inside the row's <a href="…"> made the browser
treat any click on the button as a click on the anchor, sending the
user to the document detail page even though the tag handler called
goto() with the tag-filter URL. e.stopPropagation() doesn't cancel
the anchor's default navigation.

Refactor to the stretched-link pattern: the row-wide anchor sits as an
overlay (`absolute inset-0 z-0`) and the content wrapper sits above it
(`relative z-10` + `pointer-events-none`). Tag buttons re-enable
pointer events with `pointer-events-auto`, so they're true siblings of
the anchor and receive their own clicks. Empty content areas pass
through to the anchor for whole-row navigation.

The vitest-browser client project doesn't load Tailwind CSS, so the
z-index has no effect there and Playwright's coordinate-based click
hits the anchor instead of the button. Trigger the click directly on
the button DOM element in the unit test (with a comment explaining the
test-env constraint); the actual user-facing behavior is verified via
playwright against the running dev server.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 09:10:20 +02:00
Marcel
a7efb0044c feat(documents): rebalance list row — summary + archive chips, restored sender/receiver
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m43s
CI / OCR Service Tests (push) Successful in 33s
CI / Backend Unit Tests (push) Failing after 2m57s
Refill the columns that went visually empty after the previous dedup
commit (`fc0fc57`):

- Middle column gains the document `summary` (line-clamp-2, italic,
  with `summaryOffsets` highlighting — the backend already populates
  the offsets, the frontend just wasn't rendering them) and a row of
  thin neutral chips for `archiveBox`, `archiveFolder`, and `location`
  (~99% of docs in the corpus carry these). Chips are desktop-only
  and skip empty values.
- Right column restores `VON sender` and `AN receivers`, now with
  `<mark>` highlighting that the previous right-column copy lacked,
  so search matches stay visible there.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 08:44:49 +02:00
Marcel
fc0fc57409 refactor(documents): drop duplicate sender/receiver in list rows
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m49s
CI / OCR Service Tests (push) Successful in 35s
CI / Backend Unit Tests (push) Failing after 2m52s
The desktop document-list row showed sender/receiver twice — once
side-by-side in the middle column and again stacked in the right
column. Stack the middle-column block vertically (the side-by-side
grid wasted horizontal space and competed with the larger thumbnail)
and remove the now-redundant copy from the right column.

The middle-column block keeps the search-match highlighting, which the
right-column copy never had.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 08:19:46 +02:00
Marcel
6aae159698 feat(documents): enlarge thumbnail in document-list rows
Add a `size` prop to DocumentThumbnail (default `sm` keeps the existing
60×84 tile used in person sublists; new `lg` is 120×168) and use `lg`
for the main document-list row, where the previous tile occupied less
than half of the row's vertical space.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-23 08:18:50 +02:00
Marcel
ff0bb89297 refactor(autosave): rename flushViaBeacon → flushOnUnload; add void to fire-and-forget fetch
The sendBeacon name was misleading after switching to keepalive fetch.
Also adds a test to confirm flush is a no-op when pendingTexts is empty.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 07:12:21 +02:00
Marcel
b6bfb9148e fix(frontend): use generic document icon for thumbnail fallback
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m36s
CI / OCR Service Tests (pull_request) Successful in 30s
CI / Backend Unit Tests (pull_request) Failing after 2m48s
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
Addresses @leonievoss and @felixbrandt — fix(ui): "the PDF icon
misleads for image documents" and "swap for a neutral file icon".

The fallback now shows a generic document-text glyph (page outline +
three text lines) instead of the PDF-specific icon with the folded
corner. Applies equally well to PDFs, JPEG/PNG scans, and TIFF
documents — all of which can land in the fallback path.

Also bumped the icon from h-6/w-6 to h-8/w-8 — the previous 24px
glyph looked sparse inside the 60×84 tile (Leonie, post-merge
iteration point #2).

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 23:03:19 +02:00
Marcel
04ebd2a5bd feat(frontend): render DocumentThumbnail in DocumentRow and PersonDocumentList
Home search rows and person detail sidebars now show the real
first-page preview when one exists, falling back to the PDF icon
for documents the backfill hasn't processed yet. The old `variant`
prop on PersonDocumentList is removed — it tinted the icon
differently for sent vs received, which no longer applies with a
uniform thumbnail tile.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 22:36:20 +02:00
Marcel
be184d8faf feat(frontend): add DocumentThumbnail shared 60x84 tile component
Renders the document thumbnail with object-cover + object-top so
letter salutations stay visible, empty alt (title nearby is the
accessible name), loading=lazy, decoding=async, and dark:mix-blend-multiply
for dark mode. Falls back to a PDF icon when thumbnailKey is null —
legacy documents, unsupported content types, or transient failures
all land here.

Refs #307

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 22:31:35 +02:00
Marcel
2b8032524d fix(nav): restore DocumentTopBar back button sizing and add right padding
- BackButton gains showLabel prop: showLabel=false renders icon-only with
  aria-label, no mr-2 on svg (was causing 0px button width in topbar)
- DocumentTopBar: BackButton restored to h-11 w-11 circular touch target
  with showLabel=false matching the original 44×44px <a> it replaced
- Topbar row gets pr-4 (16px right padding per spec); action buttons div
  no longer needs its own pr-3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 14:34:31 +02:00
Marcel
9e59da598e fix(nav): replace static href="/" in DocumentTopBar with <BackButton>
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m46s
CI / OCR Service Tests (pull_request) Successful in 39s
CI / Backend Unit Tests (pull_request) Failing after 3m2s
CI / Unit & Component Tests (push) Failing after 2m46s
CI / OCR Service Tests (push) Successful in 27s
CI / Backend Unit Tests (push) Failing after 2m44s
The document detail page back button was missed in the original refactor —
it still pointed to "/" (dashboard) regardless of where the user came from.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 11:27:03 +02:00
Marcel
367dcc66f2 refactor(nav): add class prop to BackButton, remove mb-4 from topbar contexts
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m41s
CI / OCR Service Tests (push) Successful in 37s
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 2m44s
CI / OCR Service Tests (pull_request) Successful in 32s
CI / Backend Unit Tests (pull_request) Failing after 2m51s
- BackButton now accepts a `class` prop (default 'mb-4') so callers can
  override spacing; resolves hardcoded margin in flex-row topbar snippets
- documents/[id]/edit and enrich/[id] pass class="" to suppress the margin
- Replace weak className unit test with class-prop behaviour tests
- Add [data-hydrated] comment in E2E spec explaining what emits the attribute

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 11:16:49 +02:00