The author column stayed at w-28 after the typography bump, wrapping
names into a cramped two-line stack. w-40 gives name and date one
comfortable line each at the full-width layout.
Refs #802
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rows kept their compact sizes (15px titles, 12px excerpts/meta) after
the overview widened to max-w-7xl, leaving the text undersized in
full-width rows. Title is now text-lg, excerpt and meta text-sm; R-1
impl-ref updated.
Closes#802
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The detail page stayed at max-w-3xl and looked narrower than every
other page. The outer container now matches the directory width
(max-w-7xl) so the sheet spans the page like Dokumente/Personen, while
an inner max-w-3xl column keeps the prose line length readable.
Refs #799
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reader interlude and the LESEREISE badge hardcoded orange-50/200/
400/700, leaving light text on a light background in dark mode. Switch
to the existing journey-tint/journey/journey-border tokens already used
by the list and editor rows.
Closes#801
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Intro, letter titles, curator annotations, and interludes rendered at
12-14px — copied from the scaled mockup sizes in LR-2. Narrative text
is now 16-18px, meta lines and links 14px; the LR-2 impl-ref table is
updated to match.
Closes#800
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reading sheet used bg-surface (white), so the document cards inside
the article had the same background as the sheet itself. The spec's
three-level hierarchy is canvas → article panel (#FAFAF7) → white cards;
introduce --color-sheet (mode-aware) and use it on the article. Also
move JourneyItemCard from bg-white to bg-surface so dark mode remaps it,
and tint the curator annotation with bg-muted so it stands off the card.
Refs #797
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The R-2 mockup shows the article on a distinct light panel, but the
impl-ref table only specified the centered container, so the page
rendered flat on the canvas. Wrap the <article> in the standard card
pattern (BackButton stays outside on the canvas) and record the sheet
in both spec impl-ref tables so mockup and impl-ref agree.
Closes#797
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Captures the read-model invariant that was only living in a code comment:
views are assembled in-transaction, entities never cross the controller
boundary in the geschichte domain. CLAUDE.md §DTOs now names the exception
so the convention text stops contradicting the code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The append dedup guard was check-then-insert: two concurrent appends of the
same document both pass exists() and both insert. The partial unique index
on (geschichte_id, document_id) WHERE document_id IS NOT NULL closes the
race; append saveAndFlush-es and maps the DataIntegrityViolationException to
the same 409 JOURNEY_DOCUMENT_ALREADY_ADDED as the friendly pre-check. The
CHECK on note length pins the 2000-char contract in the schema, mirroring
chk_text_length on transcription_blocks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GESCHICHTE_TYPE_IMMUTABLE and JOURNEY_NOTE_TOO_LONG were declared in
errors.ts, translated, and documented — but never existed in the backend.
update() now rejects a type change with 409 (omitted/same type still pass);
note length is enforced at 2000 with its own code, matching the frontend
maxlength and the i18n message (resolves the #793 discrepancy in favour of
the spec). JOURNEY_ITEM_NOT_IN_JOURNEY is deleted everywhere instead — the
deliberate 404 posture for cross-journey item ids must not leak existence
via a distinct code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the '#753 deferred' placeholder with the actual implementation:
JOURNEY edit route now opens JourneyEditor (item list, drag/move-up/down,
add bar, sidebar) while STORY route continues to use GeschichteEditor.
Add item-mutation endpoints to the relation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Interlude is a first-class editorial concept introduced by the JourneyEditor
(#753): a note-only JourneyItem with no backing document, stored in the note
column and distinguished visually by --color-interlude-* CSS tokens.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both error-handling sections now list JOURNEY_ITEM_NOT_IN_JOURNEY,
JOURNEY_NOTE_TOO_LONG, JOURNEY_DOCUMENT_ALREADY_ADDED, and
GESCHICHTE_TYPE_IMMUTABLE alongside the existing security codes.
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>
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>
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>
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>
- 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>
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>
GLOSSARY entry for NameMatches (direct vs partial name-match strength and how
the search layer maps it); person/README adds resolveByName to the public
surface. No ADR — the matching rule is localized and justified inline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture the why behind deploying Ollama to prod/staging compose: the
corrected init recipe (supersedes ADR-028 §10's never-functional curl
loop), the OLLAMA_KEEP_ALIVE=-1 pin (so a future maintainer doesn't
optimize it away and reintroduce the post-idle cold-load 503), the
30->60s timeout NFR, and the memswap==mem hard-OOM trade-off.
Addresses #759 review (Markus #3, Nora #2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The diagram declared Container(ollama, ...) twice — an alias collision that
renders a duplicate box. It also declared the backend->ollama relationship
twice. Keep the richer 'Ollama LLM Service' declaration and the more
specific 'NL query parsing (POST /api/generate)' relationship; drop the
duplicates.
Addresses #759 review (Markus #2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docker-compose.prod.yml declares the volume as `ollama-models` (hyphen),
so the compose-project-prefixed name is `archiv-production_ollama-models`,
not the underscored `archiv-production_ollama_models` the model-upgrade
guide documented. The documented `docker volume rm` would not have matched
the real volume.
Addresses #759 review (Tobias #2).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
application.yaml sets app.ollama.timeout-seconds: 60 (raised from 30 to
absorb the cold model load on the first query after an Ollama restart),
but DEPLOYMENT.md still documented 30. A doc that contradicts the shipped
value is a traceability defect.
Addresses #759 review (Markus, Felix, Elicit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Markus (architect): document SearchFilterBar + the search/ components
(SmartModeToggle, InterpretationChipRow, SmartSearchStatus,
DisambiguationPicker) and the POST /api/search/nl relation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both the first-time model pull runbook (from this branch) and the model
upgrade procedure (from main) belong in DEPLOYMENT.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers the SmartModeToggle pill (inside the search input, Google AI Mode
style), InterpretationChipRow anatomy, DisambiguationPicker, and all
status/error/empty states as full-result-area panels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses Elicit's and Sara's review concerns on PR #749:
- Expand §6 ollama_models section into a full model upgrade runbook (step-by-step
docker volume rm + recreate, including production volume name prefix)
- Add re-deploy idempotency note to §3.4 (init container exits quickly when model
already present on the volume)
- Add NL search smoke test to §3.4 (curl command distinguishing 200 from 503
NL_SEARCH_UNAVAILABLE)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updated OLLAMA_API_KEY env vars table from 0.6.5 to 0.6.5 or 0.30.6 to
match both tested versions. Added an explicit warning in §3.4 that
docker compose up -d --wait blocks for 60–90 min on first deploy when the
model pull has not yet completed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
§12 stated OLLAMA_API_KEY guards against lateral movement — contradicts
§7's empirical finding that it is not enforced. Replaced with an accurate
note referencing §7. Stale pre-merge placeholder in Consequences ("Three
TBD items must be resolved") removed; all three are resolved. §7 section
title updated from "0.6.5" to "0.6.5 and 0.30.6" to match the body text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OLLAMA_API_KEY: non-enforcement confirmed on both 0.6.5 and 0.30.6
- read_only: true: confirmed working on both 0.6.5 and 0.30.6
- Peak RSS during pull: ~108 MiB (well under 2g limit)
- All TBD placeholders resolved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all references to the CX32 VPS (8 GB RAM, Hetzner Cloud) with the
actual production server: a Hetzner Serverbörse dedicated server with an
Intel Core i7-6700 (4C/8T, 3.4 GHz) and 64 GB RAM.
Affected files:
- .claude/personas/devops.md — monthly cost line + upgrade example
- docs/infrastructure/production-compose.md — sizing section + cost table
- docs/DEPLOYMENT.md — OCR memory table + OCR_MEM_LIMIT env var description
- docs/adr/004-pdfbox-thumbnails.md — thumbnailExecutor memory ceiling note
- docs/adr/021-tmpdir-persistent-volume-staging.md — OOMKill rationale in alternatives
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both #730 (tag case-collision) and #684 (person-delete DB integrity) landed
an ADR-032 on main. Renumber the tag/case-collision one to 033 — it is
referenced only from this PR's person-domain comments and its own file, so the
move is self-contained and touches no Flyway migration. The person-delete
ADR-032 and the V71 migration comment that cites it are deliberately left
untouched (editing an applied migration would drift its Flyway checksum).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review noted the "never throws" claim was overstated: the exact-case Optional
lookups still surface a NonUniqueResultException on two byte-identical
same-case rows. That is a true data anomaly out of #731's scope (ambiguous =
case-insensitive) and resolves to the opaque INTERNAL_ERROR, never a wrong
row. Record that boundary at both resolution points and in ADR-032 so the gap
is not silently assumed covered.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
findByName resolved via Optional<Person>
findByFirstNameIgnoreCaseAndLastNameIgnoreCase, which threw
NonUniqueResultException once two people shared a first+last name case-
insensitively (hans müller / Hans Müller) — a 500 on the routine upload path
(DocumentService.storeDocument sender resolution).
findByName now resolves exact-case → single case-insensitive match → else
empty. The sender path deliberately diverges from the alias path: an
ambiguous name leaves the sender UNSET rather than guessing the lowest id,
because correct provenance beats a confidently-wrong pre-fill a reviewer
won't re-check. The two new name queries use explicit HQL equality so a null
first name binds as `= NULL` (no match) instead of the derived-query fold to
`first_name IS NULL`, which would widen a last-name-only row in as a sender.
Pins the opaque error path (IncorrectResultSizeDataAccessException stays
INTERNAL_ERROR with no Hibernate/SQL/row-count leak) and extends ADR-032 with
the Person section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>