Commit Graph

1290 Commits

Author SHA1 Message Date
Marcel
e975642a4c fix(pdf-controls): add focus-visible ring to all PdfControls buttons (WCAG 2.1 §2.4.7)
Some checks failed
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 16:09:15 +02:00
Marcel
72f422afe2 fix(a11y): increase all PdfControls buttons to 44×44px touch targets
Add min-h-[44px] min-w-[44px] to all five PDF viewer buttons (prev,
next, zoom in, zoom out, annotation toggle) and widen icon-only
padding from p-1 to p-2. Adds aria-pressed to the annotation toggle
for correct toggle semantics (WCAG 2.2 §2.5.8 + ARIA 1.2).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 16:09:15 +02:00
Marcel
cd1c0b210e test(typeahead): note resetKey smoke-test limitation in spec comment
Some checks failed
CI / Unit & Component Tests (push) Failing after 4m19s
CI / OCR Service Tests (push) Successful in 42s
CI / Backend Unit Tests (push) Failing after 3m20s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 14:27:24 +02:00
Marcel
a239c16c31 fix(documents): sync filter display state with URL on navigation
Three root causes prevented filters from reflecting the URL after SvelteKit
client-side navigation:

1. +page.server.ts now resolves sender/receiver display names in parallel with
   the document search (UUID validation + silent 404 drop), so initialSenderName
   / initialReceiverName land in server data ready for the UI to use.

2. +page.svelte passes initialSenderName, initialReceiverName, and navKey
   (incremented via untrack on every navigation) down to SearchFilterBar.
   The untrack() prevents the effect from re-running due to its own navKey write.

3. SearchFilterBar forwards navKey as resetKey to each PersonTypeahead, which
   already had a void resetKey guard added in the previous commit.

Together these ensure that after navigating to /documents?senderId=<uuid> the
typeahead shows the person's display name, and clicking × reset clears it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 14:27:24 +02:00
Marcel
8a8205ad8d fix(person-typeahead): add resetKey prop to clear term on navigation reset
When the user types in the sender/receiver typeahead without selecting a
person and then clicks ×-reset (navigating back to /documents), the
manually-typed term was not cleared because initialName stayed '' between
navigations — the existing $effect tracking initialName never fired.

Adding `resetKey` (incremented by the page on every navigation) forces
the effect to re-run via `void resetKey`, clearing searchTerm=initialName
even when initialName is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 14:27:24 +02:00
Marcel
0430383e1c fix(date-input): re-derive display when value prop changes externally
`display` was initialised once and never updated, so the text box would
show a stale German date after the parent reset `value` (e.g. × reset
button or timeline drag). A guarded `$effect` re-derives `display` from
`value` whenever the two are out of sync while preserving mid-typing
partial dates (germanToIso returns '' for incomplete input, which matches
value='' during typing → no spurious re-derive).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 14:27:24 +02:00
Marcel
586eea009b fix(build): add prerender entry for /hilfe/transkription
The SvelteKit prerender crawler cannot reach this route because
hooks.server.ts redirects all non-public paths to /login before the
crawler follows links. Explicitly listing the route in kit.prerender.entries
tells SvelteKit to render it directly without crawling.

Also removes a misleading comment that claimed the auth hook guards
prerendered static files — it does not. Prerendered HTML is served as a
static file by the reverse proxy; hooks.server.ts only runs for SSR requests.

Closes #472

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 14:25:32 +02:00
Marcel
7c2c4741ab refactor(dashboard): replace new CSS tokens with existing equivalents
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 4m0s
CI / OCR Service Tests (pull_request) Successful in 32s
CI / Backend Unit Tests (pull_request) Failing after 3m21s
CI / Unit & Component Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
mint-soft → accent-bg, line-soft → line-2, link-quiet → ink-2,
ink-4 removed (was never applied to any element).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 23:12:36 +02:00
Marcel
d464bca9f3 style(dashboard): increase doc row padding py-1.5 → py-3 in ReaderRecentDocs
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 3m57s
CI / OCR Service Tests (pull_request) Successful in 32s
CI / Backend Unit Tests (pull_request) Failing after 3m18s
CI / Unit & Component Tests (push) Failing after 3m58s
CI / OCR Service Tests (push) Successful in 32s
CI / Backend Unit Tests (push) Failing after 3m18s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 18:22:55 +02:00
Marcel
2283f733cc refactor(dashboard): align ReaderPersonChips cards with /persons overview style
Some checks failed
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
CI / Unit & Component Tests (pull_request) Failing after 4m10s
CI / OCR Service Tests (pull_request) Successful in 35s
CI / Backend Unit Tests (pull_request) Failing after 3m33s
- rounded, px-4 py-6, shadow-sm, gap-4 — matches overview card sizing
- hover: left accent border + shadow-md (matches overview hover)
- avatar: h-12 w-12, font-bold (djb2 palette colors kept)
- name: font-bold, group-hover:underline
- doc count: neutral bg-muted chip instead of mint pill

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 18:21:24 +02:00
Marcel
cc20583ae6 fix(dashboard): replace text-brand-navy dark:text-brand-mint with text-ink
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 4m7s
CI / OCR Service Tests (pull_request) Successful in 35s
CI / Backend Unit Tests (pull_request) Failing after 3m18s
CI / Unit & Component Tests (push) Failing after 4m2s
CI / OCR Service Tests (push) Successful in 37s
CI / Backend Unit Tests (push) Failing after 3m25s
text-ink uses --c-ink which is #012851 in light and #f0efe9 in dark, responding
to both @media and [data-theme='dark'] via CSS variable — no extra token needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:40:00 +02:00
Marcel
86d75d91be fix(dashboard): use bg-surface instead of bg-white in ReaderHeaderBar for dark mode
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 4m8s
CI / OCR Service Tests (pull_request) Successful in 34s
CI / Backend Unit Tests (pull_request) Failing after 3m17s
bg-white is hardcoded #fff and only flips via the Tailwind dark: media-query variant.
bg-surface uses a CSS variable (--c-surface) that responds to both the media query
and the [data-theme='dark'] attribute, matching how all other cards on the page work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:35:46 +02:00
Marcel
a98ca0e5d3 fix(dashboard): add dark:text-brand-mint to ReaderHeaderBar greeting and stat numbers
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 4m2s
CI / OCR Service Tests (pull_request) Successful in 34s
CI / Backend Unit Tests (pull_request) Failing after 3m24s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:31:14 +02:00
Marcel
1c515a3145 style(dashboard): widen stat columns from px-3 to px-5 in ReaderHeaderBar
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 4m13s
CI / OCR Service Tests (pull_request) Successful in 40s
CI / Backend Unit Tests (pull_request) Failing after 3m20s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:28:51 +02:00
Marcel
43d36c898c feat(dashboard): wire ReaderHeaderBar, grid content row, delete ReaderStatsStrip (#483)
Some checks failed
CI / Unit & Component Tests (push) Failing after 4m46s
CI / OCR Service Tests (push) Successful in 52s
CI / Backend Unit Tests (push) Failing after 3m32s
CI / Unit & Component Tests (pull_request) Failing after 4m0s
CI / OCR Service Tests (pull_request) Successful in 43s
CI / Backend Unit Tests (pull_request) Failing after 3m32s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:13:00 +02:00
Marcel
60326cfb0a refactor(dashboard): ReaderDraftsModule mint left-border, card-head, row structure (TDD, #483)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:10:20 +02:00
Marcel
e598f5a506 refactor(dashboard): ReaderRecentStories card-head link, touch targets (TDD, #483)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:07:16 +02:00
Marcel
e1c78e3fbe refactor(dashboard): ReaderRecentDocs compact card-head, mint-pill badge (TDD, #483)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 17:01:46 +02:00
Marcel
ae6355d206 refactor(dashboard): ReaderPersonChips → grid layout with mint-pill doc count (TDD, #483)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 16:56:52 +02:00
Marcel
b5f9fcfdfd feat(dashboard): add ReaderHeaderBar with greeting + stat columns (TDD, #483)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 16:54:02 +02:00
Marcel
2f48dfabd1 i18n: add reader header-bar keys, remove dashboard_badge_updated (#483)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 16:50:36 +02:00
Marcel
495210052f style: add mint-soft, line-soft, link-quiet, ink-4 tokens (#483)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 16:48:33 +02:00
Marcel
eac2356948 docs(dashboard): comment isReader discriminant with ADR-007 pointer
Felix and Elicit both flagged that the isReader formula had no
in-code explanation at the point of definition; future maintainers
adding a new permission level need a fast pointer to the architectural
rationale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
d554fc7e6b fix(a11y): darken avatar palette teal for AA contrast against white
#007596 with white initials hits ~4.5:1 — at the AA threshold for
small text. #005F74 lifts it comfortably above 5:1, matching the
contrast margin of the other four palette entries.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
7bd477d24e feat(dashboard): empty-state message for ReaderPersonChips
When the top-persons fetch returns an empty list (or fails and
degrades to []), the chip area used to render the heading and the
view-all link with nothing in between, looking like a load failure.
Adds dashboard_reader_no_persons (de/en/es) and renders it above the
chip row.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
b1c2132aa6 fix(a11y): view-all links meet 44px touch target
WCAG 2.2 §2.5.8 (Target Size, Minimum). The Alle Personen → and Alle
Geschichten → text links were inline elements with no enforced minimum
height — small tap targets on mobile. inline-flex + min-h-[44px] keeps
the visual layout while guaranteeing the 44px hit area.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
f7eefb525f fix(a11y): Aktualisiert badge passes WCAG AA contrast
text-ink-3 on bg-ink-3/10 (low-saturation grey on lighter grey) gave
roughly 2.8:1 contrast — below the 4.5:1 AA threshold for normal-weight
small text. Switching the foreground to text-ink-1 keeps the muted
background but lifts the text contrast well above 7:1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
500611925d fix(a11y): focus-visible ring on reader-dashboard view-all links
Both view-all links (Alle Personen → in ReaderPersonChips, Alle
Geschichten → in ReaderRecentStories) were missing the
focus-visible:ring-2 ring used by every other interactive element on
the reader dashboard, leaving keyboard users with no visible focus
indicator. WCAG 2.1 §2.4.7 (Focus Visible, Level AA).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
64bcc8d031 test(dashboard): cover the {#if data.isReader} render branch
Adds a readerData fixture and five render-level assertions: the three
ReaderStatsStrip totals, the recent-docs heading, the absent
contributor mission caption, and the drafts module appearing only when
canBlogWrite is true.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
5a8a1898f8 fix(dashboard): isNew compares timestamps numerically, not by ISO string
ISO strings differing only in millisecond precision or timezone
formatting represent the same instant but failed string equality, so
freshly created documents could miss the "Neu" badge depending on
whatever shape the backend serializer emitted.

Browser specs cannot run in the worktree (birpc WebSocket closure
crash documented in the PR description); the new vitest-browser test
must be verified from a normal checkout.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
b4f24f4965 fix(api): mark StatsDTO totalPersons + totalDocuments as required
Mirrors what npm run generate:api would emit against the StatsDTO
record (all three @Schema(REQUIRED) annotations). Round-1 fix only
updated totalStories; this brings the other two into line.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
797852b494 test(dashboard): add partial-failure resilience test + fix i18n Dok. key
- page.server.spec.ts: new test verifies topPersons=[] when that fetch
  rejects, rest of reader data still loads — addresses @Sara concern
- ReaderPersonChips: replaces hardcoded "Dok." with
  dashboard_reader_doc_count_suffix Paraglide key (de/en/es)
  — addresses @Felix suggestion

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
518334bc38 fix(a11y): fix WCAG AA contrast on reader dashboard "view all" links
brand-mint on white is ~2.8:1; brand-navy is ~10:1. Both "Alle Personen"
(ReaderPersonChips) and "Alle Geschichten" (ReaderRecentStories) links
updated: text-brand-navy underline hover:text-brand-mint.

Addresses @Leonie critical review finding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
c8b1a890be refactor(dashboard): extract settled<T>() helper; fix page.svelte.spec.ts types
Collapses 5x duplicated null-check pattern in the reader fetch branch into
a single typed helper — addresses @Felix review blocker.

Also adds isReader/incompleteDocs/incompleteTotal to page.svelte.spec.ts
baseData so it satisfies the discriminated PageData union introduced by this PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
1f592958d7 feat(stats): wire totalStories stat tile in reader dashboard
Manually adds totalStories to generated StatsDTO type and wires it from
readerStats into ReaderStatsStrip — resolves @Elicit: stories tile was
permanently showing "—".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
2be2087a95 feat(dashboard): wire reader layout to +page.svelte
Adds conditional {#if data.isReader} block that renders the 5-zone
reader layout (StatsStrip → DraftsModule → PersonChips → two-column
docs/stories row) for READ_ALL-only users, while preserving the
existing contributor layout for WRITE_ALL / ANNOTATE_ALL users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
4d9234244e feat(dashboard): add reader dashboard components
Adds 5 new components for the permission-gated reader layout:
- ReaderStatsStrip: stat tiles (documents / persons / stories) linking to list pages
- ReaderPersonChips: top-N persons by doc count with avatar + name
- ReaderDraftsModule: blog draft list for BLOG_WRITE users
- ReaderRecentDocs: 5 most-recently-updated docs with Neu/Aktualisiert badge
- ReaderRecentStories: 3 latest published stories with 150-char HTML-stripped excerpt

Each component ships with a vitest-browser spec covering the key assertions.
Avatar color/initials logic is inlined to satisfy $lib/shared → $lib/person
boundary rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
9b82621770 feat(i18n): add reader dashboard message keys (de/en/es)
New keys: reader stats strip, person chips, drafts module, recent docs,
recent stories, Neu/Aktualisiert badges, and all-items links.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
a58e796ffa feat(dashboard): add isReader flag + reader branch to page load
Read-only users (no WRITE_ALL or ANNOTATE_ALL) now receive lean reader
data (stats, top-4 persons, 5 recent docs, 3 recent stories, and drafts
when BLOG_WRITE) instead of the contributor transcription queues.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
6a46a1e3eb chore(api): update generated types — add UPDATED_AT sort and persons size/sort params
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 15:56:47 +02:00
Marcel
9fd1f3cde2 refactor(documents): extract timeline drag state into rune class (#385)
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 4m5s
CI / OCR Service Tests (pull_request) Successful in 38s
CI / Backend Unit Tests (pull_request) Failing after 3m22s
CI / Unit & Component Tests (push) Failing after 3m57s
CI / OCR Service Tests (push) Successful in 37s
CI / Backend Unit Tests (push) Failing after 3m22s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:50:34 +02:00
Marcel
7f99c64d45 docs(layout): note light-mode --timeline-bar-idle contrast ratio (#385)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:34:03 +02:00
Marcel
18aaf1f3e8 style(documents): gate timeline bar hover under (hover: hover) (#385)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:32:31 +02:00
Marcel
dd0a77a5a2 feat(documents): focus-visible ring on timeline controls (#385)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:30:25 +02:00
Marcel
f68d16ef58 docs(documents): explain monthBoundaryTo day-zero idiom (#385)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:28:58 +02:00
Marcel
bf501b7d62 docs(documents): mark clipBucketsToRange as @internal (#385)
The function has a single in-source call site (TimelineDensityFilter)
but is exported so timeline.spec.ts can pin its boundary semantics
without rendering the orchestrator. Note that explicitly so future
readers don't treat the export as a public API contract.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:05:46 +02:00
Marcel
5d749b2415 refactor(documents): move timeline CSS vars to layout.css (#385)
Defining --timeline-bar-idle / --timeline-bar-outside on :root from
inside a scoped <style> block leaks the contract into the global
namespace via component-local CSS, even though the selector itself
makes it work. Move both variables to layout.css next to the other
--palette / --c-* design tokens; the component <style> now only
consumes them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:05:06 +02:00
Marcel
1d6016cb19 fix(documents): pluralise timeline bar aria-label by count (#385)
The flat "{count} Dokumente / documents / documentos" keys read as
"1 Dokumente" / "1 documents" / "1 documentos" to a screen reader
when only one document falls in the month bucket. Splits each
locale into _singular + _plural keys and picks the form by count
in TimelineBars, mirroring the existing upload_banner_singular /
_plural pattern in this project.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:03:19 +02:00
Marcel
48da819a54 feat(documents): focus-visible ring on timeline bar buttons (#385)
Bar buttons rendered with bg-transparent + p-0 fell back to the
default browser outline, which is invisible against bg-surface for
keyboard users. Adds the project-standard focus ring
(ring-2/brand-navy/offset-2) so the focused bar reads as focused.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 11:00:15 +02:00
Marcel
153752a901 fix(documents): bump timeline control buttons to 44x44 (#385)
WCAG 2.5.8 (target size, AA) requires 44×44 minimum, and the
project's senior persona makes that a hard floor on desktop too.
Reset-zoom: h-6 → h-11 + min-w-[44px] + px-3.
Clear-selection: h-6 w-6 → h-11 w-11.

Two regression tests on the TimelineDensityFilter spec assert the
sized classes so a future shrink can't slip through silently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:58:59 +02:00