Rewires briefwechsel-rows.visual.spec.ts against the shared fixture
(seedBilateralPair + cleanupBilateralPair), adds afterAll cleanup,
and folds the conv-person-bar visibility gate into openBilateral()
so both the structural test and the snapshot block fail loudly on
a hero-state regression — matching the a11y spec's safety net.
Refs #305
Fixes @saraholt follow-ups 1 + 2 + 3 from PR round-2 review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Lifts the three-API-call seeding (create sender, create receiver,
create document) out of briefwechsel-a11y.spec.ts and into a
dedicated fixtures module. The spec now calls seedBilateralPair()
in beforeAll and cleanupBilateralPair() in afterAll so the test
DB doesn't accrue seeded rows across reruns.
Two caveats captured in the helper docstring: the backend has no
person-delete endpoint (only the document is purged), and the
timestamped last names make leftover persons collision-free.
Refs #305
Fixes @saraholt follow-up 1 + 2 from PR round-2 review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the seeding pattern from the a11y spec: beforeAll creates two
persons + one document so the page renders the row layout. The
structural test now asserts the ConversationThumbnail tile AND the
DistributionBar are present — a regression that drops to the hero
or breaks the row wiring fails here instead of silently passing a
hero-state check.
Snapshot block stays gated on VISUAL=1 (baselines captured during
review against a seeded backend) so the structural coverage ships
immediately and the pixel-diff coverage ships once baselines land.
Refs #305
Fixes @saraholt blocker 2 from PR review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous version navigated to /briefwechsel with no params, which
renders the hero state — axe-core scanned the hero, not the new
ThumbnailRow / ConversationThumbnail / DistributionBar. This commit
seeds two persons + one document via the API in beforeAll, then
drives the URL with ?senderId=X&receiverId=Y so each of the
36 test runs (3 viewports × 2 themes × 2 assertions) actually scans
the intended DOM. Also asserts that conv-person-bar is visible first,
so a regression that drops the page back to hero fails explicitly
rather than silently passing an empty sweep.
Refs #305
Fixes @saraholt blocker 1 from PR review
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a dedicated axe-core sweep for /briefwechsel so contrast or
semantic regressions on the new row layout fail independently of
the catch-all accessibility suite. Scoped to the main landmark so
shared chrome violations (if any) aren't double-reported.
Refs #305
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a Playwright spec gated on VISUAL=1 with one snapshot per
(mobile/tablet/desktop × light/dark) = 6 baselines. Snapshots stay
skipped in CI until the baseline set is captured and committed —
running `playwright test --update-snapshots briefwechsel-rows`
against a seeded backend generates them.
Structural check runs unconditionally so the file is wired into CI
today rather than waiting for the baseline capture step.
Refs #305
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- admin.spec: click 'Thumbnails erzeugen', wait for status DONE
within 30s, screenshot the success message
- accessibility.spec: /admin/system joins the page list so the
thumbnail card is checked in light, system-dark, and manual-dark
axe-core runs
Refs #307
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 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>
/chronik → /aktivitaeten; heading updated in all three locales.
Component folder (lib/components/chronik/) stays unchanged — internal
implementation detail, not user-facing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Seeds a document, transcription block, and block comment via API,
then visits /documents/{id}?commentId=X&annotationId=Y and asserts
the page enters transcribe mode, the comment article becomes visible,
and the URL query params are stripped. Runs at 320px and 1440px so
the collapsed PDF strip clipping on mobile is caught. An axe-core
pass guards the new tabindex + focus-visible ring against a11y
regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Happy-path journey (upload 2 PDFs → banner → CTA → /enrich) plus axe
sweep at 320/768/1440 × light/dark for the dashboard route. Seeded
docs are cleaned up in afterEach via psql so repeated runs stay green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three free axe checks light up (light / system-dark / manual-dark) without
further code changes — they run the existing parameterized spec against
/chronik.
Part of #285.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove the old conversations page that was superseded by briefwechsel/.
No navigation link pointed to /conversations; it was unreachable through
the UI. Deletes 5 files, removes 14 orphaned i18n keys from de/en/es
message bundles, and removes E2E tests that navigated to /conversations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The password-reset E2E test was using button[type="submit"].last() to target
the password change button on the profile page. The profile page has two submit
buttons with identical text, so .last() is layout-order-dependent and breaks
if the form order ever changes.
Add data-testid="submit-password" to PasswordChangeForm and use getByTestId()
in the test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- +layout.svelte: adopt main's blue header structure (accent stripe, no
border-b, bg-header instead of bg-brand-navy)
- layout.css light mode: drop --c-nav-active (removed by main); set
--c-header: #012851 (confirmed correct now that header is brand-navy)
- layout.css dark mode: drop --c-nav-active; keep navy PDF tokens and
--c-header: #012851 from our branch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change header --c-header dark value from #01335e to #012851 (brand navy):
#01335e gave 4.3:1 with ink-3 (WCAG AA fail); #012851 gives 4.99:1 (pass)
- Switch header element from bg-surface to bg-header so dark mode uses the
independent --c-header token instead of inheriting the surface background
- Fix both dark blocks (media query and manual override) to stay in sync
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Header should use bg-header (rgb(1,51,94) = #01335e) in dark mode instead
of bg-surface. Currently fails because header still uses bg-surface.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two failing test suites that encode the regressions this issue fixes:
- accessibility.spec.ts: axe wcag2aa in both prefers-color-scheme:dark
and data-theme='dark' — fails because --c-ink-3:#6b7280 on #1a1a1a = 3.2:1
- theme.spec.ts: color-scheme computed property is 'dark' in dark mode
— fails because neither dark CSS block sets color-scheme: dark
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Asserts header background is rgb(1,40,81) in light mode
- Asserts header stays navy after switching to dark mode
- Asserts logo text visible at 375px viewport
- Asserts login page has AuthHeader with navy background and lang switcher
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blockers (14):
- B1: fix senderName/receiverName to use $derived instead of $state + sync $effect
- B2: migrate all korrespondenz components from messages-extra shim to paraglide m.*
- B3: i18n CorrespondenzEmptyState (heading, subtext, search placeholder)
- B4: add response.ok checks to admin layout server load
- B5: add response.ok checks to korrespondenz page server load
- B6: add page.server.spec.ts with 5 test suites for korrespondenz load function
- B7: add axe-core accessibility checks to all e2e korrespondenz tests
- B8: add Testcontainers JPQL tests for findSinglePersonCorrespondence (DISTINCT + sender)
- B9: hide auth reset-token endpoint from OpenAPI spec; remove from generated api.ts
- B11: replace amber hardcoded hex colors in SinglePersonHintBar with brand tokens
- B12: replace clipboard emoji with Heroicons SVG in SinglePersonHintBar
- B13: create DateInput component (German dd.mm.yyyy); use it in CorrespondenzFilterControls
- B14: add Paraglide compile step to CI workflow before lint/test
Suggestions (11):
- S1: make CorrespondentSuggestionsDropdown a pure display component; lift fetch to PersonBar
- S2: fix leftover messages-extra import in ConversationTimeline; use brand tokens for status dots
- S3: add intent comment to EntityNav openFlyout behavior
- S4: rename canManageGroups → canManagePermissions throughout admin
- S6: remove domFlush helper from DateInput spec; use expect.poll instead
- S7: replace test.skip with throw new Error in bilateral e2e tests
- S8: add inverse aria-disabled test for filter strip
- S9: remove sm:min-h-0 from sort button to preserve 44px touch target
- S10: add title attributes to tablet trigger buttons in EntityNav
- S11: delete messages-extra.ts shim entirely
Also: fix admin pages revealing blank strip at bottom (-mb-6 on admin layout)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cover: empty state loads with search heading, nav link goes to /korrespondenz,
single-person mode shows hint bar, sort toggle updates dir param, bilateral mode
skips gracefully when no co-correspondents exist, swap button reflects swapped IDs
in URL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The panel was restoring its open/tab/height state from localStorage,
causing the discussion drawer to reopen on every subsequent page visit
even without a ?commentId= param. Removed all LS_KEY_* constants, the
savedOpen/savedTab/savedHeight restore logic, and the persistence
$effect. The panel now always starts closed (or opens to metadata when
the document has no file yet), and the discussion tab opens exclusively
via the commentId deep-link query param.
Also add .svelte-kit-backup/ to .gitignore and .prettierignore to
prevent lint failures from the root-owned Docker-generated directory.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- captureProofshots() now accepts an optional setup(page) callback that
runs before each screenshot's page.goto(), so localStorage can be
injected reliably without loading a backend-dependent page
- dashboard-screenshots.spec.ts seeds 2 notifications (MENTION + REPLY)
for admin via direct DB insert in beforeAll, cleans up in afterAll
- localStorage.familienarchiv.lastVisited injected directly via
page.evaluate() — no fragile document page navigation needed
- Updated screenshots committed (all 6 now show all 4 widgets)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Any future feature spec now just calls:
captureProofshots('/my-route', 'feature-name')
to get 6 screenshots (3 viewports × 2 themes) saved to
proofshot-artifacts/{feature-name}/.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces the wcag2a/wcag2aa E2E suite from the test-suite branch with
the color-contrast rule active — no disableRules exclusion. Also adds
/coverage/ to .prettierignore so generated lcov reports don't fail the
lint hook.
This commit intentionally fails the axe suite until the contrast fixes
land in the next commits.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
documents.spec.ts: replace getByText with getByRole('heading') to avoid
Svelte's #svelte-announcer matching the same text (strict mode violation).
SaveBar.svelte: move <form id="mark-for-review-form"> out of the component
and into +page.svelte as a sibling of delete-form. The form was previously
nested inside <form id="update-form">, which is invalid HTML. The browser
auto-repaired it, causing a Svelte hydration mismatch that broke the edit
form's use:enhance, preventing version snapshots from being recorded —
leaving history tests with 0 versions instead of the expected 2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Restructure the "New Document" page so users can save quickly:
- FileSectionNew becomes the first element, redesigned as a prominent
upload zone with an icon and large click target
- Title field is rendered standalone below the upload zone; it
auto-populates from the filename (via parseFilename + stripExtension
fallback) unless the user has already typed something
- All remaining metadata (who/when, description, transcription) moves
into a collapsible "Weitere Details" section that auto-expands when
URL prefill data or a form error is present, or when filename parsing
detects a date/person
- title is no longer required — the form can be saved with only a file
- DescriptionSection gains a `hideTitle` prop for use in this layout
- `form_label_title` translation key no longer carries a hardcoded `*`;
the asterisk is rendered by the template only when `titleRequired` is
set (currently only the edit form)
- E2E tests added for all three scenarios from the issue
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Test 6 (delete annotation): the mouse-draw test can create multiple
annotations in CI. Changed the assertion to `countBefore - 1` instead
of a hard-coded 0, so the test is resilient to any pre-existing count.
Test 7 (hash versioning): `[data-testid^="annotation-"]` matched both
real annotation elements AND `annotation-outdated-notice` (which also
starts with "annotation-"), inflating the count to 2 instead of 0.
Added `:not([data-testid="annotation-outdated-notice"])` to exclude the
notice from the count assertion.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code:
- Persist panelOpen to localStorage so panel stays open after reload
- Auto-open panel to Metadaten when document has no file (no prior state)
Tests:
- Nav active state: check bg-nav-active instead of text-brand-navy
(nav uses semantic tokens since dark mode refactor)
- Save button: use exact:true to avoid matching "Speichern & abschließen"
(new button was added alongside the plain "Speichern" button)
Note: annotation tests (documents.spec.ts:324, 356) are pre-existing
flaky failures due to test data contamination, not caused by this PR.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Inline <script> in app.html applies saved localStorage theme before first
paint to prevent flash of wrong theme
- ThemeToggle.svelte: moon/sun button, localStorage persistence, sets
data-theme on <html>, defaults to system preference on first visit
- Placed in +layout.svelte between language selector and user menu
- E2E tests cover visibility, toggle, reverse toggle, persistence, and
no-flash behaviour — all 6 passing
Refs #64
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep the new bottom-panel / AnnotationSidePanel architecture from this branch
while pulling in the documentFileHash / visibleAnnotations filter that was added
on main. Thread documentFileHash through DocumentViewer so outdated-annotation
filtering works end-to-end.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the left sidebar layout with:
- Full-viewport PDF/image viewer (never resizes, position: absolute)
- Fixed floating bottom panel with tabs: Metadaten, Transkription,
Diskussion, Verlauf
- Compact top bar with title, date · sender → receivers row, and
Annotieren / Edit / Download actions
- Drag-to-resize panel with localStorage persistence of open/height/tab
- Panel opens automatically to Diskussion when an annotation is clicked
- Documents without a file default to showing the Metadaten tab
New components: DocumentTopBar, DocumentViewer, DocumentBottomPanel,
PanelMetadata, PanelTranscription, PanelDiscussion, PanelHistory
PdfViewer: annotateMode and activeAnnotationId lifted to bindable props;
AnnotationCommentPanel removed (discussion moves to the Diskussion tab).
Closes#62
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The old test waited for the PDF canvas (30 s timeout) before checking
for a disabled Annotieren button — a brittle dependency that caused
consistent failure because the reader's file fetch never completed in
CI. Since issue #61 will remove the disabled button entirely for users
without ANNOTATE_ALL, rewrite the test to assert the button is absent,
which is correct both in the interim and after #61.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- System tab gains a second card with a 'Datei-Hashes berechnen' button
that calls POST /api/admin/backfill-file-hashes and shows the updated count
- i18n: admin_system_backfill_hashes_* keys added in de/en/es
- E2E: test verifies the button triggers the backfill and shows the success message
Closes#56
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Regenerate API types with fileHash on Document and DocumentAnnotation
- PdfViewer accepts documentFileHash prop; filters visibleAnnotations to
those whose hash matches (or is null) and shows an amber notice banner
when any annotations are hidden due to a hash mismatch
- Document detail page passes doc.fileHash to PdfViewer
- Add i18n key annotation_outdated_notice in de/en/es
- E2E: two new tests covering hide-on-reupload and restore-on-original-reupload
scenarios; add minimal2.pdf fixture for a different-hash upload
Closes#55
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace UI-based document setup in beforeAll hooks with direct API
calls via Playwright's request fixture — avoids the 90s timeout from
navigating + uploading through the Docker dev server
- Fix non-PDF test: create a file-less document in beforeAll instead of
relying on seed data that may not exist
- Share annotationDocId across describe blocks so the read-only user
test can navigate to a known PDF document
- Add annotation visibility check before enabling annotate mode in the
delete test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Install pdfjs-dist v5 and add optimizeDeps pre-bundle config
- New PdfViewer.svelte component: renders each page on a <canvas> with
correct device-pixel-ratio scaling, overlays a text layer (enables
text selection; foundation for annotations in #40), prev/next
navigation, zoom controls, and lazy page rendering (only current ±1
pre-fetched — avoids freezing on multi-page documents)
- Replace the <iframe> in documents/[id]/+page.svelte with PdfViewer;
image attachments continue to use <img>; detection now uses
doc.contentType instead of filename extension
- Unit tests for navigation controls and page counter (pdfjs mocked)
- E2E tests: PDF renders as canvas (not iframe), nav controls visible,
image fallback stays as <img>; minimal.pdf fixture for upload tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All three history tests navigated to the doc page but didn't wait for
SvelteKit hydration, so the toggle onclick wasn't registered yet. Also
wait for versions to load (API call) before asserting on version items
or the compare button.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three scenarios: versions list appears after edits, diff shows changed
field, compare mode displays diff between two selected versions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
waitForURL('/') resolves as soon as the URL changes but before SvelteKit
finishes hydrating — the avatar button's onclick is not yet registered,
so the click has no effect and the dropdown never opens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>