Commit Graph

1284 Commits

Author SHA1 Message Date
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
Marcel
3b6b117c75 fix(documents): cleanup timeline drag listeners on unmount (#385)
Pointerdown attaches three document-level listeners. Without an
explicit teardown, an unmount mid-drag (route change, view toggle,
viewport drops below lg) left them attached and they kept writing
to torn-down state cells.

Wrap the cleanup in $effect's return, which Svelte 5 invokes on
unmount. The listener-removal regression test pins this so the bug
cannot come back silently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:56:58 +02:00
Marcel
2e9ce8e1da fix(documents): surface timeline density fetch failures via console.warn (#385)
Previously a 5xx, network blip, or JSON parse error all collapsed
into the same silent "no buckets" rendering. The widget still
degrades gracefully — failure should not block the document list —
but operators and Sentry now see the failure in browser devtools
instead of having to reverse-engineer a missing chart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 10:55:04 +02:00
Marcel
e5739d7f8e refactor(documents): extract TimelineControls (#385)
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 4m10s
CI / OCR Service Tests (pull_request) Successful in 36s
CI / Backend Unit Tests (pull_request) Failing after 3m16s
CI / Unit & Component Tests (push) Failing after 3m54s
CI / OCR Service Tests (push) Successful in 39s
CI / Backend Unit Tests (push) Failing after 3m34s
Splits the reset-zoom and clear buttons out of the orchestrator into
their own component. Closes part 3 (final) of Felix's component-split
concern. Orchestrator now composes four single-purpose children
(TimelineBars, TimelineYAxis, TimelineXAxis, TimelineControls) and
keeps only the pointer choreography that links them.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 10:07:45 +02:00
Marcel
219d9a816e refactor(documents): extract Y-axis and X-axis components (#385)
Felix's review named "TimelineAxes" as one of four split targets.
The Y-axis and X-axis don't sit adjacent in the DOM — Y is a flex
sibling of the bars+X column — so two single-purpose components
beats a discriminator-prop component. tickIndicesFor and the
omitTickYear derivation move to TimelineXAxis where they belong.
Closes part 2 of Felix's component-split concern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 10:06:46 +02:00
Marcel
00682bac4f refactor(documents): extract TimelineBars from density filter (#385)
Splits the bar row + drag-window overlay + bar styling out of the
377-line orchestrator into a single-purpose component. The pointer
choreography (handle{PointerDown,DocumentMove,DocumentUp},
indexFromClientX, cleanupDragListeners) stays in the orchestrator
per Felix's note. Closes part 1 of Felix's component-split concern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 10:04:38 +02:00
Marcel
77d282bbeb refactor(documents): split triggerSearch by zoom semantics (#385)
triggerSearch(zoomOverride?) made the call site read "depends on
whether the source event happened to include zoomFrom/zoomTo". Splits
into triggerSearchKeepZoom() and triggerSearchWithZoom(from, to) so
the contract is explicit at every call site. Closes Felix's review
nit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 10:02:02 +02:00