buildEventLookup also collected `timeline.undated`, so an undated curated
event — which renders as a plain EventPill in the undated bucket, out of
clustering scope — still seeded clusters: its dated linked letters
scattered into year bands and each collapsed into a ✉ cross-year card
with no edit link and no spatial tie to the pill, showing the event title
twice with no relationship.
Only year-band events are collected now. Fixes review finding #7.
Refs #850
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
buildEventLookup keyed on `kind === 'EVENT' && eventId` with no type
check, so a HISTORICAL curated event with ≥1 linked letter entered the
lookup and rendered as a mint EventCluster card — silently downgrading
from the full-width WorldBand that #779 REQ-009 mandates ("world-bands
render exactly as before").
The lookup now excludes `type === 'HISTORICAL'`, so a world event always
keeps its WorldBand and its letters stay loose chronological. Closes the
spec gap pinned as REQ-014. Fixes review finding #2.
Refs #850
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
buildEventLookup maps each curated event in the (already layer-filtered)
timeline to its title; splitYearLetters partitions a year's letters into
event clusters (keyed by a linkedEventId present in the lookup) and the
loose chronological remainder. A letter linking to a filtered-out event
falls back to loose (filter-then-cluster); each letter appears once.
Refs #850