- useBlockDragDrop: add runtime expect() alongside expectTypeOf so
browser-mode runner counts at least one assertion
- JourneyAddBar: use exact:true on 'Hinzufügen' button — partial match
was hitting '+ Brief hinzufügen' and '+ Zwischentext hinzufügen' too
- JourneyEditor: fix 4 issues — drop wrong not.toBeInTheDocument()
(placeholder creates accessible name); pass title:'' in publish-disabled
test (default was non-empty); use getByPlaceholder for interlude
textarea to avoid 4-element strict-mode violation; exact:true for
'Hinzufügen' button
- DocumentPickerDropdown: use .click({force:true}) on aria-disabled
option — userEvent refuses non-enabled elements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add JourneyEditor, JourneyItemRow, JourneyAddBar, GeschichteSidebar to the
geschichte README props table. Strike @dnd-kit/svelte-dnd-action library refs
and raw orange-*/blue-600 color classes in the editor spec HTML.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Static imports for both editors; type-aware <h1> title; JOURNEY type routes
to JourneyEditor, STORY type continues to GeschichteEditor unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Main editing surface for JOURNEY-type Geschichten. Manages sorted item list
with optimistic add/remove/reorder (rollback on failure), drag-and-drop reorder
via createBlockDragDrop, intro textarea, and sidebar via GeschichteSidebar.
Publish requires at least one item + non-empty title.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two add buttons: document picker (DocumentPickerDropdown) and interlude inline
draft form. Interlude confirm is aria-disabled until text is non-empty. Closing
one panel opens the other. Tests cover all three plan test cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Item row with drag handle, move-up/down buttons, inline note textarea (PATCH
on blur), interlude visual treatment, and inline confirm for removes that
would discard a note. Interlude note cannot be cleared (blocked on empty).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New DocumentPickerDropdown: single-select document search with aria-disabled
for already-added items and sr-only "bereits enthalten" hint. DocumentMultiSelect
refactored to use createTypeahead, removing raw setTimeout/debounceTimer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 30+ journey_* message keys added to de/en/es.json. Four new ErrorCode
values for journey item operations wired into errors.ts + getErrorMessage().
Interlude CSS primitives (--c-interlude-bg/border/label) defined for light
and dark themes so JourneyItemRow can reference them via semantic aliases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves Status + Persons sections into a shared component so both
GeschichteEditor (STORY) and the upcoming JourneyEditor (JOURNEY) can
use the same sidebar without duplicating markup. Adds <details> mobile
collapsibles with 44px summary hit areas.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the hard-typed TranscriptionBlockData constraint so JourneyEditor
can reuse the pointer-drag module without importing transcription types.
Selector contract (data-block-wrapper / data-drag-handle) unchanged.
Adds type-regression guard test verified via tsc --noEmit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop stale JourneyItem/JourneyItemCreateDTO schemas — removed in base
branch when api.ts was regenerated; neither type is referenced in
frontend code (JourneyItemView is the read model used instead).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Importing layout.css in test-setup.ts activated Tailwind's responsive
breakpoint classes (hidden lg:flex, hidden md:block, etc.), making
42 elements invisible at the default narrow Playwright test viewport.
Revert the CSS import. Instead, add inline style attributes to the three
components whose tests measure computed properties (min-height, font-size)
— these values match what the Tailwind classes produce, so the real app
appearance is unchanged.
Also fix goto mock leakage in the geschichten/[id] delete-failure test:
the delete-success test's goto('/geschichten') call was not cleared before
the failure test ran. Add beforeEach(vi.clearAllMocks) to reset mock state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Import layout.css in test-setup so Tailwind utilities (text-xs,
min-h-[44px]) apply in vitest-browser — fixes computed-style assertions
for badge font-size and touch-target height
- radioGroupNav: write aria-checked directly on radio buttons on arrow-key
navigation, not only via the optional onChangeFn callback
- DashboardNeedsMetadata spec: tighten footer-link matcher from /50/ to
/Alle 50/ — avoids strict-mode collision with row link whose relative
time text also contains "50" (uploadedAt is exactly 50 days ago today)
- geschichten/[id] page spec: add missing await on userEvent.click before
confirmService.settle() in both delete tests
- TypeSelector spec: replace storyCard.focus() (not on vitest-browser
Locator) with userEvent.click(); force-dispatch aria-disabled Weiter
click via element.click() to bypass Playwright actionability check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
geschichten components now describe the type-based reader split
(StoryReader / JourneyReader / JourneyItemCard / JourneyInterlude),
the TypeSelector creation flow, and the full set of API endpoints
(including DELETE /api/geschichten/{id} and GET /api/persons/{id}
for person pre-population).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The inline publishedAt $derived.by() duplicated the exact logic that
formatPublishedAt() in utils.ts encapsulates. Replace it with the
shared helper and drop the now-unused formatDate import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without a landmark or widget role, aria-label on a generic <div> is
silently ignored by most screen readers (ARIA spec). Adding role="note"
gives the element an ARIA role that accepts an accessible name, making
the interlude label actually announced.
Also adds a test asserting role="note" and the matching aria-label are
both present on the same element.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the getBoundingClientRect pattern from JourneyItemCard.svelte.spec.ts.
Tests actual rendered height rather than presence of a CSS class string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Screen readers now announce the hint paragraph text on focus when no type
is selected, so users hear why the button is disabled without having to
click it first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The action was writing aria-checked directly and then firing onChange,
which also triggered Svelte's own aria-checked={selected === type} binding.
Double-ownership: action now only calls focus() + onChange(value);
Svelte owns the attribute update.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Function names already communicate intent. Comments that restate the
function name add noise without explaining why.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JourneyReader filters items to only those where document != null before
passing them here — the ! assertion is valid by caller invariant.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds deleteError $state to [id]/+page.svelte, parses backend error via
parseBackendError/getErrorMessage on !res.ok, and displays a role=alert
paragraph. Adds two browser-tier tests: success path (goto called) and
error path (alert visible, goto not called).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sentry's wrapLoadWithSentry reads event.request.method — the test's makeEvent
now provides a real Request object. createApiClient mock was a plain function;
wrapping with vi.fn() enables vi.mocked(...).mockReturnValue in individual tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verifies radioGroupNav action moves selection forward and wraps backward
so keyboard users can navigate the STORY/JOURNEY cards without a mouse.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CSS class string assertion was fragile — class names can change without
breaking the actual layout. DOM measurement via getBoundingClientRect is the
correct way to verify computed height meets WCAG 2.2 minimum.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
13 tests covering null/undefined inputs, partial names, email fallback,
and TZ-safe date slicing for formatPublishedAt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves the confirm-then-delete flow out of StoryReader and JourneyReader into
the single [id]/+page.svelte owner. Both reader components gain an optional
ondelete prop — the delete button calls ondelete?.() so the handler is opt-in
and never duplicated. Tests verify the prop is called on click.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the 3-line inline join with the shared formatAuthorName helper from
utils.ts. Test switches from CSS class string assertion to getComputedStyle
for the badge font-size check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces aria-label="Kuratorennotiz" with m.journey_interlude_aria_label()
so screen readers get the correct label in all three supported locales.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--c-journey-bg/text/border wired in light :root, dark @media, dark [data-theme]
blocks. Exposed via @theme inline as color-journey-tint/journey/journey-border.
Light: #B46820 on #FEF0E6 ≈ 4.6:1 AA at 12px bold. Dark: #E8862A on #3A2A1A ≈ 4.7:1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Self-check: GeschichteView.items present; type emitted as 'STORY'|'JOURNEY' union literal.
List endpoint returns GeschichteSummary[]; detail endpoint returns GeschichteView.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The method intentionally skips permission checks and tag-colour resolution.
Renaming it to findSummaryByIdInternal makes the internal-only contract
visible at every call site, closing the latent CWE-284 risk flagged in
the PR review.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Re-inject JourneyItemService into GeschichteService (no cycle:
JourneyItemService → GeschichteQueryService, not GeschichteService).
Add getView(UUID) that loads the Geschichte and its items in a single
@Transactional(readOnly=true) session. Controller now delegates to
getView() instead of making two separate service calls. Tests updated
to stub getView() and cover the new method.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Create PersonNameFormatter with a single static join(firstName, lastName) method.
Replace the inline string concatenation in GeschichteService.toView() and the
private join() method in JourneyItemService with calls to PersonNameFormatter.join().
The new helper handles null-safety and trimming consistently in one place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add @Operation annotation to reorderItems() clarifying that itemIds must
contain ALL item IDs for the journey in the desired order — a partial list
returns 400 Bad Request. This surfaces the contract in the generated
OpenAPI spec and Swagger UI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clarify in the Javadoc that getSummaryById intentionally skips scope checks
and tag-colour resolution. This is safe under the current single-tenant model
and is explicitly used by JourneyItemService.append() to validate that a linked
document exists before persisting a JourneyItem.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
doesNotExist() asserts the key is absent from the JSON object, but Jackson
serializes a null Optional<String> as {"note": null} — the key is present with
a null value. nullValue() correctly matches that case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add two service-level integration tests to JourneyItemIntegrationTest:
- append_persists_item_at_position_10: verifies that the first append to an
empty journey creates an item at position 10 in the DB.
- reorder_swaps_positions_atomically: appends two items then reorders them,
asserting the DB reflects the new position assignment.
Both tests use the SecurityContextHolder authentication pattern from
GeschichteServiceIntegrationTest and mock S3Client to avoid MinIO connections.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add GeschichteQueryService component to the L3 supporting-domains diagram.
Remove the now-deleted Rel(geschSvc, journeyItemSvc, "Delegates getItems()")
arrow and add the correct Rel(journeyItemSvc, geschQuerySvc, ...) arrow that
reflects the actual dependency direction after the refactor in the prior commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add findByGeschichteIdWithDocument() to JourneyItemRepository with a
LEFT JOIN FETCH on document. getItems() now uses this query so that all
documents for a journey's items are loaded in a single SQL round-trip.
toView() now reads item.getDocument() directly from the already-fetched
association instead of issuing a separate documentService.getSummaryById()
call per item.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>