- Remove dead `message` field from both frontend ImportStatus types
(field is now @JsonIgnore'd on the backend)
- Extract failure message ternary into `$derived` — business logic off
the template (Felix)
- Add motion-reduce:animate-none to spinner — WCAG 2.1 SC 2.3.3 (Leonie)
- Replace text-green-600 with text-green-800 — WCAG AA contrast 6.1:1
on bg-green-50 (Leonie)
- Add min-h-[44px] to all three buttons — WCAG 2.2 44px touch target (Leonie)
- Add 6 missing tests: IMPORT_FAILED_INTERNAL path, IDLE state text,
null importStatus, ontrigger called on DONE/FAILED/IDLE buttons (Sara)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts the mass-import block from +page.svelte into ImportStatusCard.svelte.
Changes per the three UX fixes from issue #533:
- RUNNING: animated spinner (animate-spin) + processed count at text-base;
auto-poll at 2 s was already in place
- DONE: processed count at text-base, label at text-xs uppercase tracking-widest
- FAILED: maps statusCode (IMPORT_FAILED_NO_SPREADSHEET / IMPORT_FAILED_INTERNAL)
to Paraglide messages — no raw German backend string rendered
Adds vitest-browser tests covering spinner visibility, count display,
and per-statusCode FAILED message selection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use [key: string]: unknown index signature so TS does not reject the
extra fields (location, status) passed to the redirect/failure result
in the spec helpers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirror the groups/new fix: replace inline beforeNavigate/isDirty with
createUnsavedWarning() + UnsavedWarningBanner and add an enhance callback
that calls clearOnSuccess() before update() on redirect results.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Use createUnsavedWarning() + UnsavedWarningBanner to replace the inline
beforeNavigate/isDirty pattern, and add an enhance callback that calls
clearOnSuccess() before update() so the guard is disarmed before
SvelteKit's internal goto() fires on a redirect result.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five test files mocked $lib/shared/services/confirm.svelte under BOTH
spellings (.svelte and .svelte.js) within the same file; two more mocked
only the .svelte.js form. Both resolve to the same module URL but register
two distinct Playwright route handlers in @vitest/browser-playwright. The
cleanup logic only removes one, leaving an orphan that fires when the next
session loads the module — crashing the run with
"[birpc] rpc is closed, cannot call resolveManualMock".
This is the exact trigger fixed upstream by vitest PR #10267 (issue #9957).
Normalise every confirm.svelte mock to the no-extension form, matching
production imports and the source file basename (confirm.svelte.ts).
After this commit: 8 confirm.svelte mocks across 8 spec files, all under
one canonical ID. A meta-test (next commit) prevents the duplicate-id
pattern from reappearing.
Refs: #553 · vitest-dev/vitest#9957 · vitest-dev/vitest#10267
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Production code referenced $lib/shared/services/confirm.svelte under two
spellings — 4 files with the .js extension and one without. Standardise on
the no-extension form to match Svelte 5 rune-module convention and the
source file basename (confirm.svelte.ts).
Why this matters: vitest browser mode's @vitest/browser-playwright resolves
both spellings to the same module URL but registers a separate Playwright
route per spelling. The route-cleanup logic only unregisters the latest,
leaving an orphan that crashes the next session with
"[birpc] rpc is closed, cannot call resolveManualMock". Fixed upstream in
vitest PR #10267 (merged, not yet released). Normalising the spelling
removes the trigger from our side.
Refs: #553. Companion test-file changes follow in the next commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces 15 setTimeout sleeps with vi.waitFor on the actual signal
(fetch URL recorded, banner appears, status text rendered) and
switches the default fetch mock from mockResolvedValue to
mockImplementation so each call yields a fresh Response — no more
"body stream already read" unhandled rejections.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the vacuous expect(true).toBe(true) sleep test with a real
flyout-open assertion (role=dialog appears after trigger click) and
turns the Escape-keydown smoke test into a full open→Escape→closed
behavioral test. Routes the Escape event through document (matches
the svelte:document binding) instead of window.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Status color paths (exhausted/expired/revoked), new-invite form
toggle, loadError banner.
5 new tests covering ~10 branches.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Color dot hidden at depth>0 and when color is null, document count
badge omitted at 0, toggle click mutates collapseMap.
4 new tests covering ~6 branches.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds banner-hidden defaults (success/error), empty groups list,
groups field undefined fallback to [].
4 new tests covering ~6 branches.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds unsaved-warning hidden by default, oninput dirty marker, form
error banner hidden when form is undefined.
3 new tests covering ~6 branches.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds backfill-versions and backfill-file-hashes click handlers,
verifies initial fetch hits import-status and thumbnail-status.
3 new tests targeting ~10 branches in the page component.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Heading with tag name, name input hydration, color picker visible only
for top-level tags, color swatch grid (10 entries), aria-pressed for
active color, success banner branch, error banner branch, merge-success
banner branch.
8 tests covering ~30 branches in the tag-edit page.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
admin/ocr index: heading, sender-models heading, global-history link,
defensive defaults for missing trainingInfo fields.
admin/ocr/[personId]: person name from personNames lookup, Unknown
fallback when not found, back-link href, missing-personNames defensive
handling.
8 tests across two pages.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three admin/index pages (groups/tags/users) — each renders a single
"Wähle X aus der Liste" prompt for the desktop split-view layout.
AuthHeader: brand link href + wordmark.
PersonsEmptyState: empty heading + explanation text.
6 tests across five small files.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tag link href, document-count visibility branch, color-dot at depth 0
vs deeper, aria-current matrix, children list rendering, collapse-map
hides children, expand/collapse toggle for nodes with children.
9 tests covering ~30 branches in the recursive tree-node component.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backfill cards rendered, both backfill buttons enabled by default,
no success banner before any action. Smoke-level coverage of the
admin maintenance page.
5 tests covering basic render branches.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All-sections render when full permissions, users/invites hidden when
!canManageUsers, groups hidden when !canManagePermissions, tags hidden
when !canManageTags, system/ocr hidden when !canRunMaintenance,
flyout closed by default.
6 tests covering ~30 branches in the permission matrix.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
register page (350 lines): hero render when no codeError, NO_INVITE_CODE
vs other-codeError card branches, form hidden when codeError set,
back-to-login link, form section rendering, prefill hydration of
firstName/lastName/email, prefill-hint visibility branch, hidden
code input with code-null fallback.
admin/users/new: heading, three card sections, group checkboxes
rendered, form-error banner branch, cancel link, submit button.
17 tests across two pages.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Heading with email, three card sections (profile/groups/password),
success vs error form banners, group preselection from editUser.groups,
cancel link, delete button. Mocks the confirm service.
7 tests, ~25 branches.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
admin/groups/new: heading, both permission group renderings (4 standard
+ 4 administrative checkboxes), form-error banner branch, cancel link
href, submit button form-attribute wiring, name input requiredness.
Mocks $app/navigation so beforeNavigate doesn't crash the test runner.
enrich/+: heading, empty placeholder vs populated count + start CTA,
start CTA href derived from documents[0].id, per-row title rendering,
bulk-select checkbox gated on canWrite.
16 tests across two files.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
users/[id]: full-name derivation across all four branches
(both/firstName-only/lastName-only/email fallback), avatar initials
matrix, email/contact row visibility tied to data presence.
admin/ocr/global: heading + back link, runs prop pass-through,
defensive default for missing history fields.
geschichten/[id]: title rendering, author full-name vs email fallback
vs null, publishedAt suffix conditional, persons and documents sections
gated on array length, edit/delete actions gated on canBlogWrite. Mocks
the confirm service since it requires a ConfirmDialog mounted in layout.
26 tests across three files.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each status (active / exhausted / revoked / expired) maps to a distinct
visual treatment via statusColor() — one focused test per branch
asserts the correct background class on a tbody element so the test
verifies user-observable behaviour rather than the internal switch.
Also covers: empty placeholder, loadError banner, filter chip
selection state, new-invite form toggle on button click, createError
message visibility inside the open form, created-invite success card
with shareable URL, revoke button gating to active invites only,
unlimited-uses display, no-expiry display.
16 tests, ~50 branches covered.
Refs #496.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes all remaining failing tests in the browser project. Root cause in
every case: Playwright CDP-based clicks/keyboard events do not reliably
trigger Svelte 5 onclick/onkeydown handlers. Pattern applied throughout:
- Buttons / result items: native `.element().click()` or
`dispatchEvent(new MouseEvent('click', { bubbles: true }))`
- Keyboard events: `dispatchEvent(new KeyboardEvent('keydown', { key }))`
on the target DOM element
- TipTap selection: `element.focus()` + Selection API +
`document.dispatchEvent(new Event('selectionchange'))`
- ProseMirror focus for onFocus: `dispatchEvent(new FocusEvent('focus'))`
Also fixes pre-existing content/logic issues found during analysis:
- ChronikErrorCard, BulkDropZone, CorrespondenzHero: stale i18n strings
and wrong ARIA role (combobox not textbox)
- RichtlinienRuleCard: beide beispielInput + beispielOutput required for
arrow to render; querySelectorAll to get last code element
- admin/system/page: vi.unstubAllGlobals() in afterEach; strict-mode
heading selector; per-call mockResolvedValueOnce for dual-card page
- DocumentList: add total prop + result count paragraph (test relied on it)
- PersonTypeahead keyboard navigation: pressKey() helper with native
KeyboardEvent dispatch replaces userEvent.keyboard()
- PersonMultiSelect: native element clicks for result selection and
chip removal; keydown dispatch on result div for Enter key test
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TranscriptionEditView: fix 4 failing tests:
- textarea → [role="textbox"] selector (editor is contenteditable, not <textarea>)
- button clicks → dispatchEvent(MouseEvent) for reliable Svelte 5 onclick with TipTap
- mentionedPersons test: init block with @mention token so deserialize() creates a
mention node; use userEvent.type + vi.waitFor (real timers) instead of fill +
fake timers, which prevents TipTap onUpdate from firing the debounce timer
EntityNavSection: anchor link click → add capture-phase preventDefault before
clicking to stop iframe navigation while allowing Svelte onclick handler to run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CLEANUP-2 (#413): convert two actionable TODOs to issue-referenced stubs
- +layout.server.ts:29 → TODO(#453) for dedicated admin stats endpoint
- ChronikRow.svelte: TODO(#454) for commentPreview; keep SECURITY line
as standalone comment (XSS guard stays co-located with the risk)
CLEANUP-3 (#414): add one-line justification comments to both naming
violators — SecurityUtils and GlobalExceptionHandler are both justified
by framework convention; no rename needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>