Commit Graph

611 Commits

Author SHA1 Message Date
Marcel
822a2fac3a fix(ui): add inner padding to strip components
Some checks failed
CI / Unit & Component Tests (push) Failing after 1m59s
CI / Backend Unit Tests (push) Failing after 3m2s
CI / Unit & Component Tests (pull_request) Failing after 1m18s
CI / Backend Unit Tests (pull_request) Failing after 2m29s
Add px-3 to person bar, filter controls, and hint bar so inputs
don't sit flush against the container edge.

Refs: #179

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:53:03 +02:00
Marcel
fbf5e9f178 refactor(ui): remove CorrespondenzEmptyState, replaced by CorrespondenzHero
Refs: #179

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:50:07 +02:00
Marcel
d5e3de5fe6 fix(ui): constrain results state to max-w-7xl like other overview pages
Move strips inside the max-w-7xl container so person bar, filter
controls, and hint bar are no longer full-bleed. Remove duplicate
side padding from strip components — the parent container handles it.

Refs: #179

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:49:30 +02:00
Marcel
7b2324ecfb fix(ui): unify strip padding and bump person bar inputs to h-12
Align person bar, filter controls, and hint bar side padding to
px-4 sm:px-6 lg:px-8, matching the standard layout of all other
overview pages. Override person bar inputs from compact h-9 to h-12
for better touch targets in the results state.

Refs: #179

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:46:36 +02:00
Marcel
f39d9e6f30 feat(ui): two render states — hero vs results — with unified padding
Hero state (no senderId): centred CorrespondenzHero with discovery
headline, cross-link, large typeahead, recent persons. No person bar
or filter controls shown. Results state (senderId set): full-width
strips then content area with max-w-7xl responsive padding matching
other overview pages. Removes focus delegation hack.

Refs: #179

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:43:54 +02:00
Marcel
e9acd44acb feat(ui): add CorrespondenzHero with discovery headline and large typeahead
New centred hero component for the Briefwechsel page: headline
"Wessen Briefe möchten Sie lesen?", cross-link to document search,
h-14 PersonTypeahead, and recent persons chips. Adds `large` prop
to PersonTypeahead and `conv_hero_crosslink` message key.

Refs: #179

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:37:58 +02:00
Marcel
efac704d59 feat(i18n): rename Korrespondenz → Briefwechsel in all languages
Update nav label, page heading, empty-state headline, and document
link text. German uses "Briefwechsel", English "Letters", Spanish
"Cartas". Empty-state headline now uses the discovery framing from the
design discussion.

Refs: #179

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:23:47 +02:00
Marcel
a9228d156f refactor(ui): rename route /korrespondenz → /briefwechsel
Update all internal links (AppNav, CoCorrespondentsList, goto) to the
new URL. No redirect needed — no production URLs exist yet.

Refs: #179

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 19:22:22 +02:00
Marcel
a863f8baad docs(search): explain void sort/dir ESLint workaround in SearchFilterBar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:50:52 +02:00
Marcel
1f86e6e238 fix(a11y): bump result count text to text-base (16px) for senior readability
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:50:00 +02:00
Marcel
c82bd61ad4 feat(a11y): fix SortDropdown accessibility — label, aria-label i18n, chevron
- Add sr-only <label> for the sort <select> (WCAG 1.3.1)
- Replace hardcoded German aria-label with Paraglide sort_dir_asc/desc keys
- Add custom SVG chevron overlay to restore visual dropdown indicator
  (appearance-none had removed the native browser arrow)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:49:06 +02:00
Marcel
56f7282a9d test(search): add empty-receivers edge case for RECEIVER sort
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:45:01 +02:00
Marcel
110024245d docs(search): document in-memory sort tradeoff and total=size() limitation
Add TODO comment explaining why SENDER/RECEIVER sort is in-memory
(JPA INNER JOIN drops null-sender docs) and note that pagination
will require a DB COUNT query in DocumentSearchResult.of().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:41:17 +02:00
Marcel
972048d57d fix(search): treat null sender.lastName as empty in sort key
A sender with lastName=null produced sort key "null Bob" which sorted
before names starting with lowercase letters (n < s, t, u, v...).
Now returns "" for null lastName, which the comparator places at end.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:39:30 +02:00
Marcel
1c1ab0c72a feat(search): reject invalid dir parameter with 400
Previously any value other than ASC/DESC silently defaulted to
DESC with no feedback. Now returns 400 Bad Request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:34:38 +02:00
Marcel
6ac3f6b176 refactor(search): remove dead SENDER case from resolveSort switch
SENDER and RECEIVER are handled by in-memory sort before resolveSort
is called, making those switch cases unreachable. Removed and added
a comment making the invariant explicit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:31:39 +02:00
Marcel
12023513b2 refactor(search): move DocumentSort from model/ to dto/
DocumentSort is a query parameter enum, not a JPA entity.
Placing it in model/ violated the layer boundary — model/ should
contain only domain entities.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 16:29:35 +02:00
Marcel
79250fb705 fix(ui): fix SortDropdown height alignment — appearance-none + items-stretch
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:57:35 +02:00
Marcel
fc3496abb6 fix(ui): align SortDropdown styling with SearchFilterBar button style
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:55:55 +02:00
Marcel
0e13fd194b feat(search): show spinner in search input while navigation is in-flight
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:52:37 +02:00
Marcel
023b6ddb49 fix(search): tagQ alone now triggers search mode; selecting chip clears tagQ
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
- isDashboard was ignoring tagQ so typing in tag filter showed dashboard
- addTag now calls onTextInput('') to clear tagQ when a chip is selected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 15:46:54 +02:00
Marcel
bc397048b7 fix(search): use in-memory sort for SENDER to include documents with null sender
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
INNER JOIN from Sort.by("sender.lastName") was excluding docs without a sender.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:15:03 +02:00
Marcel
07dbe152e2 feat(search): show result count and term-aware empty state in DocumentList
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 14:03:16 +02:00
Marcel
78fdb01ec1 feat(search): wire sort/dir/tagQ state into page.svelte and URL params
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:58:53 +02:00
Marcel
769937e03d feat(search): read sort/dir/tagQ from URL and unwrap DocumentSearchResult envelope
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:53:54 +02:00
Marcel
4fe10e1316 feat(search): add sort/dir/tagQ props to SearchFilterBar with SortDropdown
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:50:45 +02:00
Marcel
eeb78c98ec feat(search): add onTextInput callback to TagInput for live tag filter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:42:08 +02:00
Marcel
aeed6e0dac feat(search): add SortDropdown component with direction toggle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:39:26 +02:00
Marcel
3f8f3cd938 feat(i18n): add sort, result count, and empty-state translation keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:35:46 +02:00
Marcel
2c0748d60e feat(utils): add debounce utility with full test coverage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:23:14 +02:00
Marcel
d1ad4d834c chore: regenerate API types with search envelope and new sort/tagQ params
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:21:41 +02:00
Marcel
879435c8d9 feat(search): wrap search response in { documents, total } envelope
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:17:08 +02:00
Marcel
c2b5008c66 feat(search): add sort param (DATE/TITLE/SENDER/RECEIVER/UPLOAD_DATE) and tagQ filter
- DocumentSort enum validated by Spring MVC (400 for unknown values)
- SENDER sort uses Spring Data Sort on sender.lastName/firstName
- RECEIVER sort uses in-memory sort by first receiver alphabetically
- UPLOAD_DATE sort uses createdAt; default sort is DATE DESC
- tagQ param wired to hasTagPartial specification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:13:06 +02:00
Marcel
beca2d463a feat(search): extend hasText to match sender/receiver/tag names, add hasTagPartial
- hasText now JOINs sender (LEFT JOIN) and uses EXISTS subqueries for
  receivers and tags to avoid duplicate rows
- hasTagPartial added for live debounced tag text filter (ILIKE partial match)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 13:07:39 +02:00
Marcel
e6f12e6d90 docs(design): add sort integration specs for issue #180
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
Exploration spec (sort-integration-spec.html) covers 4 placement variants
with comparison matrix. Final spec (sort-inline-final-spec.html) locks in
Variant A (inline sort in search bar row) with full desktop/mobile states,
dropdown interaction anatomy, loading/empty states, and backend wiring checklist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 12:09:00 +02:00
Marcel
8e48e67cb8 fix(a11y): increase contrast
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
2026-04-06 11:24:57 +02:00
Marcel
c18ad25514 remove e2e from pipeline. takes too long 2026-04-06 11:22:08 +02:00
Marcel
e89d8a4ca9 test: increase coverage 2026-04-06 11:20:57 +02:00
Marcel
f359c19e4c fix: bump comment text to text-base + reload annotations on block delete
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
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>
2026-04-05 23:40:23 +02:00
Marcel
ef11cbee4e feat(transcription): clicking annotation focuses corresponding block
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
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>
2026-04-05 23:36:06 +02:00
Marcel
676d3cb6a7 fix(pdf): prevent scroll-sync effect from hijacking page navigation
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
The scroll-sync $effect was re-triggering on every dependency change
(including currentPage), forcing the PDF back to the annotation's page
when the user clicked next/prev. Fix: track prevActiveAnnotationId
and only scroll when the active annotation actually changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:27:52 +02:00
Marcel
d389dc2023 feat(annotations): dim non-active annotations when a block is focused
When activeAnnotationId is set, the active annotation stays at full
opacity with a highlight box-shadow, while all other annotations fade
to 30% opacity (300ms ease transition). When no block is focused,
all annotations show at full opacity.

Prop chain: activeAnnotationId flows from PdfViewer → AnnotationLayer.

2 new tests (RED/GREEN):
- dims non-active annotations when activeAnnotationId is set
- shows all at full opacity when no activeAnnotationId

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:26:02 +02:00
Marcel
b4212f5e86 feat(transcription): mobile stacked layout + cross-page scroll-sync
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
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>
2026-04-05 23:13:27 +02:00
Marcel
c22f2e41b1 fix(transcription): replace broken HTML5 drag with pointer-based drag
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
HTML5 drag-and-drop didn't work — the grip handle couldn't initiate
drag properly. Replace with pointer event-based drag:

- Grip handle pointerdown starts drag, captures pointer
- Pointermove tracks offset, shows floaty style (shadow, scale, ring)
- Turquoise drop indicator line appears between blocks at cursor position
- Pointerup finalizes: reorders array and calls PUT /reorder endpoint

Visual feedback:
- Dragged block: shadow-xl, ring-2 ring-turquoise/40, scale 1.02, opacity 0.9
- Drop indicator: turquoise h-1 rounded bar between blocks

6 new TranscriptionEditView tests:
- renders blocks in sort order
- shows next-block CTA
- shows empty state
- move-up disabled on first block
- move-down disabled on last block
- drag handle present on each block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:07:42 +02:00
Marcel
7d2d615e0c feat(transcription): add drag-and-drop + arrow button reordering
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
TranscriptionBlock:
- Desktop: grip handle (⠿) on left side, serves as drag handle
- Mobile (<768px): ▲/▼ arrow buttons (44px tap targets) replace grip
- isFirst/isLast disable boundary arrows
- onMoveUp/onMoveDown callbacks for arrow button clicks

TranscriptionEditView:
- HTML5 drag-and-drop on block wrappers (only initiates from grip handle)
- Dragged block shows 40% opacity
- On drop: reorder array and call PUT /reorder endpoint
- Arrow handlers: swap adjacent blocks and call reorder endpoint

5 new tests:
- drag handle element present
- move-up disabled when isFirst
- move-down disabled when isLast
- onMoveUp fires on click
- onMoveDown fires on click

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 23:00:52 +02:00
Marcel
4a88b3ba82 feat(transcription): add dashed next-block CTA below block list
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
Shows a muted dashed-outline box after the last block:
"Markiere eine weitere Passage im Scan, um Block N anzulegen"
Guides new users on how to create additional blocks.

Matches the spec's empty block CTA design (S1, bottom of block list).
i18n key transcription_next_block_cta added for de/en/es.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 22:52:15 +02:00
Marcel
6dc81ef2e3 fix(ui): match delete icon size + add cursor-pointer to interactive elements
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
- Comment delete icon: h-3 w-3 → h-4 w-4 (matches block delete icon)
- Add cursor-pointer to: comment delete button, Kommentieren button,
  block delete button, own-comment click-to-edit text
- Add title tooltip on comment delete button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 22:46:41 +02:00
Marcel
cef1810700 fix(comments): stop Escape propagation in edit mode
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
Pressing Escape while editing a comment now only cancels the edit,
without propagating to the parent (which closes the transcribe panel).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 22:43:37 +02:00
Marcel
351f31b183 feat(comments): inline edit on click + trash icon for own comments
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
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>
2026-04-05 22:42:24 +02:00
Marcel
e6432846a1 fix(topbar): use brand navy for transcribe button, not turquoise
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled
Transcribe button now uses border-primary/bg-primary/text-primary-fg
matching the other action buttons (Bearbeiten). Turquoise is reserved
for annotation overlays and block focus borders on the PDF.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 22:35:57 +02:00