Cluster event letters inline in the chronological /zeitstrahl (no grouping toggle) #850
Closed
opened 2026-06-15 20:18:40 +02:00 by marcel
·
0 comments
No Branch/Tag Specified
main
feat/issue-856-page-header
feat/issue-859-meta-line
feat/issue-858-card
feat/issue-860-empty-state
feat/issue-861-status-dot
feat/issue-855-avatar
feat/issue-857-segmented-control
feat/issue-850-inline-event-clustering
devops/issue-848-fork-exit-timeout
feat/issue-827-zeitstrahl-grouping
feat/issue-837-relationship-edit-dates
feat/issue-818-renovate-nightly-audit
feat/issue-803-geschichten-document-filter-chip
worktree-feat+issue-738-nl-search-backend
feat/issue-286-notification-bell-form-actions
feat/issue-580-sentry-backend
fix/issue-593-management-port-zero
worktree-feat+issue-557-upload-artifact-v3-pin
worktree-chore+issue-556-drop-client-branches-coverage-gate
fix/issue-514-prerender-crawl-bakes-redirects
fix/issue-472-prerender-entries
feat/issue-395-readme
feat/issue-345-bulk-mark-reviewed
feat/issue-344-bell-tooltip
feat/issue-341-annotieren-contrast
feat/issue-225-bulk-metadata-edit
feat/issue-317-bulk-upload
feat/issue-271-dashboard-redesign
docs/issue-240-mission-control-spec
refactor/issues-193-200
feat/issue-162-korrespondenz-redesign
feature/68-new-document-file-first
feat/81-discussion-discoverability
feat/62-document-bottom-panel
feature/56-backfill-file-hashes
feat/38-document-edit-history
fix/svelte5-test-delegation-and-login-auth
No results found.
Labels
Clear labels
P0-critical
P1-high
P2-medium
P3-later
audit
bug
cleanup
collaboration
conversation
descoped
devops
documentation
epic
feature
file-upload
legibility
needs-discussion
needs-review
notification
person
phase-1: security
phase-2: container-images
phase-3: prod-compose
phase-4: spring-prod-profile
phase-5: backups
phase-6: deployment-docs
phase-7: monitoring
redesign-mappe
refactor
security
spec-required
test
ui
user
Blocks a core user journey, causes data loss, or is a security/accessibility barrier. Work on this before P1+.
Significant friction on a main user journey; workaround exists but is non-obvious. Next up after P0.
Noticeable improvement; doesn't block anything; schedule opportunistically.
Cosmetic or parking-lot work; fine to stay open indefinitely.
Read-only audit / assessment work; produces a report rather than changing code
Something isn't working
Removal of dead code, vague comments, naming violations, and scratch artifacts
We want to extend the family archive to have more collaboration tools
We will do that later
README, ARCHITECTURE, GLOSSARY, CONTRIBUTING, per-domain docs
Marker for epic-level issues that group multiple child issues
Codebase Legibility Refactor — preparing the codebase for human evaluation and bus-factor reduction
Has an open decision or design question that must be resolved before implementation can start.
Spec is drafted and awaiting the multi-persona /review-issue gate.
Security hygiene — must be done first
Production-ready multi-stage Docker images
Production compose overlay + Caddy reverse proxy
Spring Boot production configuration profile
Database and object storage backup strategy
.env.example, DEPLOYMENT.md, runbook
Prometheus, Loki, Grafana, Alertmanager
Mappe visual redesign — consistency alignment across the app (shared components + pages). See the design_handoff bundle.
Code restructuring without behaviour change
Feature whose contract is its issue body (EARS REQ-NNN); the spec must pass review before implementation.
UI/UX and accessibility issues
Milestone
No items
No Milestone
Zeitstrahl — Family Timeline
Projects
Clear projects
No project
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: marcel/familienarchiv#850
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
As a family reader I want a curated event's letters to appear together under that event on the chronological /zeitstrahl, while all other letters stay in plain chronological order — so the timeline reads narratively without a mode switch.
Milestone: Zeitstrahl — Family Timeline (#14)
Supersedes: #827 (and PR #847). #827 shipped a
Datum · Ereignis · Themagrouping toggle; after building it we decided a single, toggle-free chronological view reads better. This issue keeps #827's event-card clustering and its computedlinkedEventId, and drops the toggle, the Thema mode, and the "Weitere Briefe" drawer. Branch off currentmain(which never carried the toggle — #847 is not merged).Context & Why
/zeitstrahl(#779/#833) is a single centered-axis chronological timeline: derived life-events and curated-event pills + world-bands sit centered on the spine; letters alternate left/right and fold into a month-density strip (YearLetterStrip) once a year holds >12. #835/PR #838 added per-letter root-tag chips; #780 added a client-side layer filter; #842 added the curator "+ Ereignis" CTA + edit pencils.This issue makes one change to that view: a curated event that has letters linked to it becomes a contained card (the event is the card's header; its letters sit inside) instead of a bare pill, and those letters are pulled out of the loose chronological flow into the card. Every other letter is unchanged — it stays a loose, alternating, density-folding chronological letter. There is no grouping control: clustering is automatic and always on.
The letter→event association is the computed
linkedEventIdfrom #827 (the curated event whosedocumentsset contains the letter's document; reuses thetimeline_event_documentsjoin; no new column/migration).User Journey
A reader opens
/zeitstrahl. In 1916 they see the curated event "Ein gewaltiger Stadtbrand" rendered as a mint-bordered card — its header carries the ★ glyph, title, "6. Juli 1916 · kuratiert", and (for a curator) an edit ✎ — with its linked letters listed inside (the first 5, then "+ N weitere Briefe anzeigen"). Just below, ordinary letters from 1916 that belong to no event sit on the spine alternating left/right, and because there are many, they fold into the familiar month-density strip. The multi-year event "Briefe von der Front" shows its card (with the pill chrome) in its own year and a lighter labeled card "✉ Briefe von der Front · 1917" in 1917 holding that year's front letters. Derived life-events (Geburt/Tod/Heirat) and world-bands (Verdun, Somme) render exactly as before. There is no mode switch anywhere.Requirements
/zeitstrahlshall render a single chronological timeline with no grouping-mode control (noDatum/Ereignis/Thematoggle, no Thema mode, no "Weitere Briefe"/"Ohne Thema" drawer).linkedEventId) in a given year band, the view shall render that event as a contained card whose header is the event (accent glyph + sr-only label, title,{date} · {kuratiert|abgeleitet}subtitle, and — for a viewer withWRITE_ALL— an edit link to/zeitstrahl/events/{eventId}/edit) and whose body lists that year's linked letters as compact cards.aria-expanded, ≥44×44px) that reveals/collapses the rest.YearLetterStripmonth-density strip when a band holds >12 such loose letters (unchanged #779/#833 behavior).TimelineEntryDTOshall carry, forLETTERentries, a nullablelinkedEventId(UUID; the curated event whosedocumentsset contains the letter's document), assembled in one batched membership pass inTimelineServiceover the events it already loads — no per-letter query, no new column, no Flyway migration (carried over from #827; not@Schema(requiredMode = REQUIRED)). When a document is referenced by more than one curated event, the link shall resolve deterministically (earliest event date, theneventId), independent of repository iteration order.{...}escaping;{@html}shall never appear in anylib/timeline/component (CWE-79; grep gate).messages/{de,en,es}.json; the grouping/Thema keys introduced by #827 (timeline_grouping_*segments,timeline_bucket_no_topic, etc.) shall be removed if not reused.GET /api/timelinefails to load, the view shall surface the existing localized error state viagetErrorMessage(code)(unchanged #779).HISTORICALshall always render as its full-width world band (per #779 REQ-009), never as a contained event card, even when letters link to it; those letters render as loose chronological letters. (Added during PR review to close a spec gap the Requirements Engineer flagged: REQ-002 did not distinguish PERSONAL vs HISTORICAL.)Acceptance Criteria
role="radiogroup"/data-testid="grouping-control"renders; notimeline-bucketwithdata-bucket-kind="fallback"/"tag"renders.documentsinclude a same-year letter renders onedata-testid="event-card"whose header contains the event title once and (withcanWrite) anevent-editlink; the letter renders inside it.bucket-show-moretoggle; clicking shows 8, clicking again 5.event-edit, no pill); the 1916 event card is unaffected.EventPill/WorldBand, no card.YearLetterStripwhose count is 15 (not 18).TimelineServiceTestassertslinkedEventIdset for a linked letter and null for an unlinked one in one batched pass; a letter in two events'documentsresolves to the same (deterministic) event id regardless of input order; regeneratedfrontend/src/lib/generated/api.tsshowslinkedEventId?.<img onerror>renders inert;grep -rn '@html' frontend/src/lib/timeline/→ zero.messages.spec.tsparity passes; no removed grouping key is still referenced.WorldBand(nodata-testid="event-card"); the letter renders as a loose chronological letter, not inside a card.event-cardrenders before the November loose letter in DOM order.Out of Scope
linkedEventId(no migration, no new endpoint).Data Model / Contract
None changed.
GET /api/timelinekeeps its path/method/READ_ALL.TimelineEntryDTOgains the computed nullablelinkedEventId(reusestimeline_event_documents, ADR-040; one batched pass;@BatchSize(50)). Runnpm run generate:apiand commit the regeneratedapi.ts.Security Considerations
Read-only; no new mutating endpoint, no new authn/authz clause.
linkedEventIdexposes only an event id aREAD_ALLreader already sees. Primary control: REQ-010{...}escaping + thelib/timeline{@html}grep gate.Notes
main. The backendlinkedEventId+ the event-card frontend (LetterBucketevent-header path, compactLetterCard, first-5/show-more) can be cherry-picked from the supersededfeat/issue-827-zeitstrahl-groupingbranch; the toggle,GroupingControl,BucketHeaderChip, Thema buckets, and the fallback drawer are not carried over.