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>
GeschichteService.getById() now returns the Geschichte entity (with the
DRAFT visibility guard intact). The controller calls journeyItemService.getItems()
and geschichteService.toView() to assemble the GeschichteView, removing the
need for GeschichteService to hold a reference to JourneyItemService.
Tests updated accordingly: GeschichteServiceTest tests toView() directly;
GeschichteControllerTest stubs both service calls; integration test uses the
two-step pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JacksonConfig was deleted (empty placeholder) — remove the now-stale
import and @Import reference from the controller slice test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds de/en/es translations for the case where a JourneyItem's linked
document has been deleted (document field is null), so the UI PR can
display a meaningful fallback string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the per-item save() loop in reorder() with a single
saveAll() call, reducing database round-trips for large journeys.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JourneyItemService no longer injects GeschichteRepository directly.
GeschichteQueryService gains findById() so JourneyItemService.append()
can load the Geschichte entity via the service layer, satisfying the
cross-domain layering rule.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- JourneyItemService.append(): replace VALIDATION_ERROR with GESCHICHTE_TYPE_MISMATCH (409 conflict)
for non-JOURNEY type guard and JOURNEY_AT_CAPACITY (409 conflict) for 100-item cap
- JourneyItemServiceTest: update assertions to expect the new specific error codes
- CLAUDE.md: expand geschichte/ package table entry with GeschichteQueryService and journeyitem/ sub-domain
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes GLOSSARY position-step value (1000→10), adds DEFERRABLE constraint note,
and documents GeschichteView, JourneyItemView, and DocumentSummary read-model types.
ADR-035 records the decision to use Optional<String> for three-way PATCH semantics
instead of jackson-databind-nullable (which targets Jackson 2.x and is incompatible
with Spring Boot 4.0 / Jackson 3.x).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds JOURNEY_ITEM_NOT_FOUND and JOURNEY_ITEM_POSITION_CONFLICT to the frontend
ErrorCode union and getErrorMessage() switch. Adds de/en/es translations.
Regenerates api.ts from the current OpenAPI spec (needs a second run once the
backend is restarted with the new endpoints compiled in).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DocumentSummary: lean document projection for journey item embedding —
skips tag-color resolution (getSummaryById), includes receiverCount
(0 when no receivers, non-null). JourneyItemView: response record for
item CRUD and GET. GeschichteView: detail response with summarised
author {id, displayName} to prevent AppUser email/group leak.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DEFERRABLE INITIALLY DEFERRED allows mid-transaction position swaps
during reorder (checked at COMMIT, not per-row). CHECK (position > 0)
guards against off-by-one in the append path. Both verified by
JourneyItemConstraintsTest via raw pg_constraint query + jdbcTemplate
inserts against a real postgres:16-alpine container.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds JOURNEY_ITEM_ADDED, JOURNEY_ITEM_REMOVED, JOURNEY_ITEMS_REORDERED
(last is ROLLUP_ELIGIBLE — drag-heavy editing produces many events).
Adds JOURNEY_ITEM_NOT_FOUND (404) and JOURNEY_ITEM_POSITION_CONFLICT
(409) to ErrorCode for IDOR protection and concurrent-edit feedback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Registers JsonNullableModule globally so JsonNullable<String> in
JourneyItemUpdateDTO can distinguish absent (unchanged) from explicit
null (clear field) on PATCH operations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GeschichtenCard.svelte: use GeschichteSummary instead of Geschichte
(list endpoint returns summaries; no items/createdAt/updatedAt needed)
- GeschichtenCard.svelte.test.ts: factory returns GeschichteSummary with
lean author shape; drop Geschichte-only fields (createdAt, groups, etc.)
- geschichten/[id]/+page.svelte: add focus:outline-none focus-visible:ring-2
focus-visible:ring-focus-ring to journey item document links (WCAG 2.4.7)
- page.svelte.test.ts ([id]): replace stale documents[] factory field with
items[]; test now checks placeholder text + note caption
- page.svelte.test.ts (new): remove removed initialDocuments from baseData;
rename test to reflect that only initialPersons is passed through
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
item.note is editorial prose — it must not be used as the anchor label.
Always show the i18n placeholder as the link text; render note as a
caption below the link when present.
Adds TODO(#786) comment so the stub degradation is tracked.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CLAUDE.md: add Geschichte and JourneyItem rows to the Domain Model table
- GeschichteSummary: add @Schema(requiredMode=REQUIRED) to getId, getTitle,
getStatus, getType, and AuthorSummary.getEmail so the TypeScript generator
emits non-optional fields when api.ts is next regenerated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- db-orm.puml: replace geschichten_documents with journey_items, add type column to geschichten, bump schema version to V72
- l3-backend-3g-supporting.puml: update GeschichteController and GeschichteService descriptions to mention STORY/JOURNEY subtypes and JourneyItem
- geschichten/[id]/+page.svelte: replace raw UUID fallback with m.geschichten_document_link_placeholder() i18n key
- messages/{de,en,es}.json: add geschichten_document_link_placeholder translation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ARCHITECTURE.md: expand geschichte domain description — two subtypes
(STORY/JOURNEY), JourneyItem ownership, ON DELETE SET NULL FK note
- GLOSSARY.md: add JourneyItem and Lesereise terms; update Geschichte
entry to mention type discriminator
- db-relationships.puml: replace geschichten_documents with journey_items
(ON DELETE CASCADE to geschichten, ON DELETE SET NULL to documents)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GeschichteEditor.svelte.spec.ts: remove docFactory + initialDocuments test;
rename documentIds test to personIds-only; add familyMember+provisional to
personFactory (were pre-existing omissions)
- GeschichtenCard.svelte.spec.ts: add type:'STORY', replace documents:[] with
items:[], change body null→undefined to match Geschichte schema
- GeschichtenCard.svelte.test.ts: add status/type/createdAt/updatedAt to factory;
cast result as Geschichte to avoid spread-widening type inference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api.ts: add GeschichteType, JourneyItem, GeschichteSummary schemas;
remove documentId param from list endpoint; change list response to
GeschichteSummary[]; add type + items to Geschichte; remove documents field
- GeschichteEditor: remove DocumentMultiSelect + documentIds from payload
(journey items are managed via the future Lesereisen editor, not here)
- GET /geschichten page: remove documentId filter from server load + URL logic
- geschichten/new: remove documentId pre-population from server load
- geschichten/[id]: replace g.documents with g.items (document-backed JourneyItems)
- geschichten/new + [id]/edit: remove documentIds from submit payload type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GeschichteService.list() now returns List<GeschichteSummary> via JPQL
projection query; accepts (status, personIds, limit); DRAFT clamp for
non-BLOG_WRITE users; AND-semantics person filter with sentinel UUID guard
- GeschichteService.getById() is @Transactional(readOnly=true) and calls
Hibernate.initialize(g.getItems()) to force-init the LAZY bag under
open-in-view=false
- GeschichteRepository: add findSummaries() JPQL query with person subquery
- GeschichteController.list(): remove documentId param, change return type
to List<GeschichteSummary>
- GeschichteSpecifications: remove hasDocument() and documentSubquery() —
TODO left for lesereisen-editor follow-on
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lesereisen-reader-spec.html — Issue #752
LR-0 type selector on /geschichten/new
LR-1 REISE badge on the list
LR-2 Journey reader (ordered cards, interlude asides, no position numbers)
lesereisen-editor-spec.html — Issue #753
LE-1 empty JourneyEditor layout
LE-2 editor with mixed items (documents + interludes, drag handles)
LE-3 inline note-editing state
LE-4 mobile layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Task 1: Create standalone FastAPI service scaffold with models, test framework,
and documentation. Includes ParseRequest, ParseResponse Pydantic models matching
OllamaExtraction contract, plus three passing tests validating model validation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Drop unused MAX_CANDIDATES constant (not referenced in service)
- Keep detached-entity safety comment in resolveTags()
- Add 3 new partial-name match tests (23a/b/c) from #763
- Use resolveByName() API in test 28 (replaces findByDisplayNameContaining)
- Add NameMatches glossary entry from #763
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Assert that when the same person id is returned by two different token
fetches, the person appears exactly once in the result -- pinning
fetchPool's putIfAbsent dedup so a future refactor can't silently
double-classify a candidate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AC#4 (maiden alias -> direct) and AC#5 (alias first name -> fetchable +
classifiable) were each split across PersonRepositoryTest (the fetch) and
PersonServiceTest (the classifier with stubs) -- nothing walked
searchByName -> resolveByName end-to-end on real Postgres. Add two tests
in the existing @DataJpaTest slice that build a real PersonService over
the autowired repositories, persist a person with a MAIDEN_NAME alias and
one with an alias firstName, and assert both classify as direct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The trigger hardcoded the multiple-people label for every count, so a
single did-you-mean picker announced "Mehrere Personen gefunden" to
screen readers while sighted users saw one name and a "Meintest du …?"
heading. Derive the trigger's accessible name from persons.length: a
single suggestion reuses the heading prop, two or more keep the
multiple-people label. Visible truncated name span unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>