Commit Graph

3584 Commits

Author SHA1 Message Date
Marcel
2cd74690ce docs(journey): annotate note field as unsanitized plain text in JourneyItem and JourneyItemView
Add Javadoc to the note field warning renderers not to use @html or equivalent
unsafe output — the value is stored verbatim without sanitization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 13:37:07 +02:00
Marcel
fe6c3850c7 refactor(geschichte): extract NIL_UUID sentinel as named constant
Replace inline UUID.fromString("00000000-...") in GeschichteService.list()
with a named constant NIL_UUID and add a test that verifies the sentinel
is forwarded to the repository when no person filter is provided.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 13:37:07 +02:00
Marcel
cdea8af290 fix(journey): detect duplicate document via PSQLException SQLState 23505
Replace fragile constraint-name string match in isDuplicateDocumentViolation()
with a check on PSQLException.getSQLState() == "23505" (unique_violation), so
constraint renames can never silently break the 409 response. Update and extend
JourneyItemServiceTest to use real PSQLException wrapping in all DIVE scenarios.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 13:37:07 +02:00
Marcel
8eb4a0ffde fix(geschichten): correct three bugs found in PR review
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m36s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 5m21s
CI / fail2ban Regex (pull_request) Successful in 52s
CI / Semgrep Security Scan (pull_request) Successful in 26s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m5s
- Pass validated documentId (not raw) to /api/geschichten — Spring backend
  declares @RequestParam UUID documentId; invalid strings cause HTTP 400
- rebuildUrl no longer deletes documentId unconditionally; clearAll and
  removeDocument handle their own documentId deletion explicitly, so
  removePerson/addPerson now preserve an active document filter
- Use ?? instead of || when falling back from doc.title to originalFilename,
  preserving an intentionally-empty title rather than overwriting it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:27:19 +02:00
Marcel
1de10986c3 feat(geschichten): integrate DocumentFilterChip into list page
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m21s
CI / OCR Service Tests (pull_request) Successful in 24s
CI / Backend Unit Tests (pull_request) Successful in 4m48s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 26s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m9s
Add DocumentFilterChip to the filter bar, extract emptyMessage as $derived.by() with person-wins precedence, and add removeDocument navigation helper. Update tests: add document-filter chip and empty-state-precedence suites, fix person-chip click test to use native element.click() + vi.waitFor() for reliable Svelte 5 onclick triggering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:06:38 +02:00
Marcel
dcc9a25fdc feat(geschichten): add DocumentFilterChip component with spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:04:58 +02:00
Marcel
03e0dae5aa feat(geschichten): resolve document title in loader, return documentFilter object
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:04:58 +02:00
Marcel
7b858e5afd feat(geschichten): add i18n keys for document filter chip and empty state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:03:12 +02:00
Marcel
db081b0488 docs(timeline): add Zeitstrahl visual specs (global Concept A, event editor)
Visual design specs for Milestone #14:
- zeitstrahl-global-concepts.html — A/B/C exploration of the global timeline
- zeitstrahl-final-spec.html — canonical Concept A (global + per-person Lebensweg)
- zeitstrahl-event-editor-spec.html — curator event editor + document quick-action

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 10:03:12 +02:00
Marcel
ebe4198be9 docs(timeline): add Person date+precision migration as foundational issue
Replace Person birthYear/deathYear integers with birthDate/deathDate +
DatePrecision so known exact birthdays render precisely. Migration,
re-import preservation rule, and bounded blast radius captured; becomes
issue 1 the timeline's derived events depend on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 10:03:12 +02:00
Marcel
57aeb1ec7b docs(timeline): add family timeline (Zeitstrahl) design spec
Hand-curated, year-banded vertical timeline weaving derived person
life-events, curated personal/historical events, and date-placed
letters. Includes proposed sub-issue breakdown for a milestone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 10:03:12 +02:00
Marcel
09df6862c0 chore(api): regenerate TypeScript types after GET /api/geschichten annotation
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m9s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 4m48s
CI / fail2ban Regex (pull_request) Successful in 44s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m7s
Adds @description JSDoc strings to status, personId, documentId and limit
query params in api.ts. Types (shapes) are unchanged — only description
strings added (refs #794).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:48:28 +02:00
Marcel
56935b4331 docs(geschichte): annotate query parameters on GET /api/geschichten
Adds @Parameter descriptions to status, personId, documentId, and limit
on GeschichteController.list() so Swagger UI shows semantics including the
permission-gated PUBLISHED override and DRAFT author-scoping for status,
the AND-filter semantics for personId, and the default/cap for limit (refs #794).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:42:27 +02:00
Marcel
2568a02d5a test(geschichte): add documentId, limit and status routing tests to GeschichteControllerTest
Adds list_passesDocumentIdFilterToService, list_passesLimitToService and
list_passesStatusFilterToService — characterisation tests for existing
parameter routing that had no controller-level coverage (refs #794).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:41:05 +02:00
Marcel
6e1ba13ca2 chore(frontend): ignore src.main/ in prettier
src.main/ is an untracked generated artefact that was not covered by
the existing ignore patterns, causing pre-commit lint failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 07:40:24 +02:00
Marcel
f65296172f test(geschichte): document FK-load-bearing cleanup order (#805)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m28s
CI / OCR Service Tests (pull_request) Successful in 25s
CI / Backend Unit Tests (pull_request) Successful in 4m57s
CI / fail2ban Regex (pull_request) Successful in 53s
CI / Semgrep Security Scan (pull_request) Successful in 28s
CI / Compose Bucket Idempotency (pull_request) Successful in 37s
Adds a comment on the @AfterEach deletion sequence explaining that journey_items
must be removed before their referenced documents and geschichten.

Addresses @sara review on PR #806.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 19:23:33 +02:00
Marcel
6230deaa96 docs(architecture): draw JourneyItemDocumentDeleteListener event flow (#805)
Adds the JourneyItemDocumentDeleteListener component and the
DocumentDeletingEvent edge (documentSvc -> listener) to the supporting-domains
l3 diagram. This is the first event-driven edge in the backend; the diagram
must make that in-process coupling visible per the doc-currency rule.

Addresses @markus review (blocker) on PR #806.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 19:22:52 +02:00
Marcel
00baf0d881 refactor(geschichte): make flush-before-delete explicit on note-less cleanup (#805)
Adds flushAutomatically = true to @Modifying on deleteNoteLessByDocumentId so
the flush-before-bulk-delete contract is explicit instead of relying on
Hibernate AUTO flush-mode behaviour.

Addresses @markus review on PR #806.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 19:22:12 +02:00
Marcel
84c5ff5b3f docs(audit): correct DOCUMENT_DELETED payload javadoc to none (#805)
The call site passes a null payload and carries the id in the documentId
column (matching FILE_UPLOADED), so the javadoc claiming Payload:
{"documentId": "uuid"} misdescribed the audit schema. Audit javadocs are the
contract forensic queries are written against.

Addresses @felix, @nora and @elicit review on PR #806.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 19:21:31 +02:00
Marcel
e9b8920b3b refactor(document): import ApplicationEventPublisher instead of FQN field (#805)
Replaces the fully-qualified org.springframework.context.ApplicationEventPublisher
field declaration with an import + simple type name, consistent with every
other field in DocumentService.

Addresses @felix review on PR #806.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 19:20:49 +02:00
Marcel
c2b98e6e84 fix(document): record actorId on DOCUMENT_DELETED audit (#805)
deleteDocument now threads the acting user through to the audit log so
DOCUMENT_DELETED records who deleted the document, matching every other
audited write path (storeDocument, updateDocument, applyBulkEditToDocument,
attachFile). Tightens the AC-7 assertion from any() to the concrete actor.

Also adds the missing ApplicationEventPublisher @Mock to DocumentServiceTest,
which the publishEvent call (added with the cascade fix) left null.

Addresses @nora (CWE-778, blocker), @felix and @sara review on PR #806.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 19:17:52 +02:00
Marcel
eefc67bd81 docs(adr): ADR-038 — domain event drives note-less journey-item cleanup on document delete (#805)
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m49s
CI / OCR Service Tests (pull_request) Successful in 31s
CI / Backend Unit Tests (pull_request) Failing after 6m43s
CI / fail2ban Regex (pull_request) Successful in 58s
CI / Semgrep Security Scan (pull_request) Successful in 24s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m7s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 17:55:38 +02:00
Marcel
44869d64f7 fix(geschichte): delete note-less journey items before document delete to prevent chk constraint 500 (#805)
Publishes DocumentDeletingEvent from DocumentService.deleteDocument before
deleteById; JourneyItemDocumentDeleteListener handles it synchronously so
note-less items are gone before ON DELETE SET NULL fires on note-carrying rows.
Plain @EventListener chosen over AFTER_COMMIT (fires too late) and @Async
(breaks rollback atomicity) — see ADR-038. Adds DOCUMENT_DELETED to AuditKind.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 17:54:54 +02:00
Marcel
7ca6492fc0 test(geschichte): rename journey items integration test — drop misleading end_to_end suffix (#795)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 6m9s
CI / OCR Service Tests (pull_request) Successful in 28s
CI / Backend Unit Tests (pull_request) Successful in 5m28s
CI / fail2ban Regex (pull_request) Successful in 48s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m10s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:15:24 +02:00
Marcel
9ea21f60ea test(geschichte): replace waitForDebounce sleep with retrying locator waits (#795)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:14:16 +02:00
Marcel
a6184fa121 test(geschichte): add 403, catch-path, and CSRF header coverage for StoryDocumentPanel (#795)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:12:43 +02:00
Marcel
232721214d refactor(geschichte): CSS.escape in focus queries, announceTimer cleanup, warning icon (#795)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:11:12 +02:00
Marcel
d91bedbaaf fix(geschichte): restore focus to item remove button after failed DELETE rollback (#795)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:09:26 +02:00
Marcel
8e4810d5da chore(i18n): delete dead keys geschichte_editor_dokumente_heading/hint (zero usages) (#795)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:07:33 +02:00
Marcel
05652a18ee docs(adr): ADR-037 — journey_items serves both Geschichte subtypes (#795)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:06:39 +02:00
Marcel
6b2dd2f259 docs(glossary): expand JourneyItem and Geschichte STORY definitions — both subtypes (#795)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:05:42 +02:00
Marcel
f43df6082d docs(architecture): update JourneyItemService C4 description — no type guard, both subtypes (#795)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:04:49 +02:00
Marcel
2121d8469f docs(geschichte): add StoryDocumentPanel to component inventory + C4 diagram (#795)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m9s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 4m23s
CI / fail2ban Regex (pull_request) Successful in 48s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m6s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:41:11 +02:00
Marcel
e8437b79d1 feat(geschichte): wire StoryDocumentPanel into the story editor sidebar (#795)
GeschichteSidebar gains optional geschichteId/items props and renders the
panel only when geschichteId is set. GeschichteEditor derives both from
its existing GeschichteView prop — null on /geschichten/new, so the panel
stays hidden there (create-then-edit, same as journeys). JourneyEditor's
sidebar call site is untouched, so journeys never show the panel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:39:48 +02:00
Marcel
4f0a660cb8 feat(geschichte): StoryDocumentPanel — sidebar document management for stories (#795)
Sidebar-section-styled panel (p-4 card, mobile <details> accordion, no
inner scroll clamp) that lists a story's journey items in position order.
Add is pessimistic via POST /items; remove is optimistic with snapshot
rollback via DELETE /items/{id}; both through csrfFetch. Already-linked
documents are unselectable in the reused DocumentPickerDropdown (visible
label wired via inputId). Document-less items (ON DELETE SET NULL)
render as removable placeholder rows. 409 capacity/duplicate map to
story-worded messages, everything else through getErrorMessage(). Add/
remove are announced in a polite live region and focus moves to the
previous row's remove button (picker input when the list empties).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:35:32 +02:00
Marcel
48f2c67ffc feat(i18n): story document panel keys in de/en/es (#795)
Panel header, hint, empty state, picker label + placeholder, deleted-
document placeholder, remove-button label, live-region announcements,
and the story-worded capacity/duplicate errors (the generic
JOURNEY_AT_CAPACITY / JOURNEY_DOCUMENT_ALREADY_ADDED messages say
"Lesereise" — the wrong frame inside a STORY panel).

Side effect: the JSON round-trip collapsed three pre-existing duplicate
keys per locale (identical values, last-wins either way) —
error_geschichte_title_too_long, error_geschichte_intro_too_long,
person_unknown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:25:32 +02:00
Marcel
4961c74a01 feat(document-picker): optional inputId prop for external label wiring (#795)
The input always carries an id (generated doc-picker-input-{uid} default,
mirroring the listbox id scheme). When a caller passes inputId it wires a
visible <label for> — the aria-label fallback is dropped then so the
visible label wins. JourneyAddBar stays untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:23:45 +02:00
Marcel
d0fc8ce995 feat(geschichte): allow journey items on STORY-type Geschichten (#795)
Delete the JOURNEY-only type guard in JourneyItemService.append() so the
existing item endpoints serve both Geschichte types. GeschichteType has
exactly two constants, so an allowlist replacement would be unreachable
dead code. Fix the not-found messages that claimed "Journey", and remove
the now-orphaned GESCHICHTE_TYPE_MISMATCH error code end to end
(ErrorCode, errors.ts union + mapping, i18n keys in de/en/es).

Tests: three STORY append unit tests written red against the guard, plus
end-to-end STORY coverage (append+retrieve, V72-style position-gap rows
incl. removal, dangling document-deleted item). The two STORY-rejection
tests die with the guard — no third enum value exists to feed it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 12:21:33 +02:00
Marcel
73b10e6f8c refactor(journey-editor): extract appendItem from the two add handlers
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m13s
CI / OCR Service Tests (pull_request) Successful in 22s
CI / Backend Unit Tests (pull_request) Successful in 4m24s
CI / fail2ban Regex (pull_request) Successful in 46s
CI / Semgrep Security Scan (pull_request) Successful in 27s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m5s
handleAddDocument/handleAddInterlude were identical except the POST
body. Behavior unchanged — all 35 editor specs stay green.

Review round 3: Felix suggestion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:33:41 +02:00
Marcel
ac6da154da docs: round-3 currency pass
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 4m17s
CI / OCR Service Tests (pull_request) Successful in 22s
CI / Backend Unit Tests (pull_request) Successful in 4m9s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 23s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m7s
- extractText.ts states the real safety invariant (output is text-only;
  JOURNEY intros arrive unsanitised by design)
- geschichte README: stale glyph/pill cells updated, formatAuthorName no
  longer claims an email fallback, formatDocumentMetaLine documented
- reader spec HTMLs: bg-white list-card cell and the mobile BottomSheet
  rows struck with the implemented decision (inline metabar actions)

Review round 3: Nora (2), Markus (2), Felix, Elicit (3).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:31:17 +02:00
Marcel
66e9309d8a fix(geschichte-ui): a11y and visual round-3 batch
- JourneyItemCard: 'Brief öffnen' back to a >=44px touch target with the
  height regression spec restored
- GeschichteListRow: REISE badges text-[10px] -> text-xs; drop the
  hardcoded aria-label and the mobile badge's aria-hidden so phone screen
  readers learn a row is a Lesereise; mobile avatar initials -> color dot
- detail page: badge text-xs, metabar Edit/Delete h-9 -> h-11, avatar
  color keyed by name to match the list
- JourneyReader: dead border-subtle class -> border-line-2
- DocumentPickerDropdown: aria-controls only while the listbox exists
- JourneyAddBar: aria-expanded/aria-controls on both toggles + focus
  hand-off into the revealed picker input / interlude textarea
- GeschichteSidebar: section h2s hidden below sm (summary already shows
  the label there)
- JourneyCreate: bg-brand-navy -> semantic bg-primary/text-primary-fg;
  title maxlength=255
- JourneyItemRow interlude: neutral frame border + left accent only,
  token utilities instead of arbitrary var() syntax and inline style

Review round 3: Leonie (1-8 + round-1 leftovers), Elicit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:30:59 +02:00
Marcel
836c9594d4 refactor(person): one author-name fallback, localized
joinNameOrUnknown()/unknownPersonName() in personFormat.ts replace the
three hand-rolled '[Unbekannt]' literals (geschichte/utils,
GeschichtenCard, personOption) — the fallback is now the i18n key
person_unknown (de/en/es), and formatAuthorDisplayName localizes the
server-side literal on the pass-through path.

Review round 3: Felix, Markus, Leonie (7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:30:23 +02:00
Marcel
07725cfa39 fix(typeahead): close() cancels the pending debounce
An abandoned query no longer fires a stale fetch after the dropdown
closed, and loading can't stay stuck on true. Test pins both.

Review round 3: Felix suggestion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:29:25 +02:00
Marcel
b4fcbd7efc fix(geschichte): uniform onSubmit rejects-on-failure contract
The c3afd57e fix made the edit page's handleSubmit throw on !res.ok but
only JourneyEditor caught it. Now: GeschichteEditor.save() catches and
keeps its dirty state (no unhandled rejection -> no GlitchTip noise on a
failed STORY save); StoryCreate throws on failure so a failed STORY
create no longer silently disarms the unsaved guard; both handleSubmit
implementations catch network rejections, surface a message, and rethrow.
Contract documented on both editors' Props. GeschichteEditor also gets
the title maxlength=255. Spec: rejecting onSubmit is caught and the
editor stays usable.

Review round 3: Felix §2, Tobias S1, Nora (3), Markus (concern 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:29:08 +02:00
Marcel
8995b6e922 fix(journey-editor): note UX, error codes, list semantics, a11y labels
- handleNotePatch routes failures through failureMessage() so a backend
  JOURNEY_NOTE_TOO_LONG renders its specific message in the row
- handleNoteBlur: '' vs undefined no-op guard (no spurious PATCH
  {note:null}), empty blur collapses the textarea, clearing an existing
  note collapses after the PATCH lands (spec LE-3)
- 'Notiz hinzufügen' toggle gets aria-expanded and moves focus into the
  revealed textarea
- journey_remove_item_aria interpolates the item title (de/en/es); dead
  journey_drag_aria_label key deleted from all locales
- editor item list is an <ol> (screen readers announce the ordering)
- editor title input gets maxlength=255 + border-danger error cue; intro
  textarea gets maxlength=4000
- Briefmeta line (date · von X an Y) under document titles in the editor
  row via the shared formatDocumentMetaLine (spec LE-2)
- new specs: successful save clears the unsaved warning; item add does
  not arm the guard; server-confirmed order after successful reorder;
  blur-without-typing no-op; focus hand-off into the note textarea

Review round 3: Sara (1-3), Felix, Elicit (LE-2/LE-3), Leonie.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:28:38 +02:00
Marcel
b9bed19610 feat(geschichte): bound title and JOURNEY intro at all three layers
Title: requireTitle() turns the VARCHAR(255) DB bound into a friendly 400
(GESCHICHTE_TITLE_TOO_LONG). JOURNEY intro: MAX_INTRO_LENGTH = 4000 in
bodyForType() plus a V75 CHECK as atomic backstop (defensive clamp first,
STORY bodies exempt) — GESCHICHTE_INTRO_TOO_LONG. Both codes wired through
ErrorCode.java, errors.ts, getErrorMessage and de/en/es. DB-layer boundary
pins added: exactly-2000 note insert (V74 CHECK) and 4000/4001 intro
inserts against real Postgres. Docs: error-code lists, db puml diagrams.

The matching maxlength attributes land with the component commits.

Review round 3: Markus (b), Nora (1), Sara (DB 2000 boundary).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 08:28:08 +02:00
Marcel
98805f596a fix(db): make V74 self-healing on databases that ran the base branch
Dedup DELETE + note clamp before the DDL so the unique index and CHECK
cannot fail mid-migration (failed Flyway row -> backend boot loop) on a
DB that served writes under the old code (no dedup guard, 5000-char
notes). No-ops on a clean database.

Note: this changes V74's checksum — dev databases that already ran V74
on this branch need 'flyway repair' (or a fresh DB). CI and prod run
from V73 or clean and are unaffected.

Review round 3: Tobias (1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:51:20 +02:00
Marcel
2e9902e8a7 fix(journeyitem): translate only the dedup-index violation to 409
Any other DataIntegrityViolationException at flush (e.g. an FK violation
on a concurrently deleted document) is rethrown instead of being
mislabeled as JOURNEY_DOCUMENT_ALREADY_ADDED. Match on the
uq_journey_items_geschichte_document constraint name.

Review round 3: Markus (a), Felix suggestion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 07:49:31 +02:00
Marcel
0884a1abcd fix(geschichten): widen list meta column so text-sm names fit
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m49s
CI / OCR Service Tests (pull_request) Successful in 24s
CI / Backend Unit Tests (pull_request) Successful in 4m6s
CI / fail2ban Regex (pull_request) Successful in 45s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m8s
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>
2026-06-10 22:55:29 +02:00
Marcel
50def73d80 fix(geschichten): scale list row typography to the full-width layout
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m57s
CI / OCR Service Tests (pull_request) Successful in 23s
CI / Backend Unit Tests (pull_request) Successful in 4m9s
CI / fail2ban Regex (pull_request) Successful in 47s
CI / Semgrep Security Scan (pull_request) Successful in 22s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m6s
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>
2026-06-10 22:53:40 +02:00