From c23ff6026e1a845d90c6bfc82b5e7188f883a3d6 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 15 Jun 2026 22:11:23 +0200 Subject: [PATCH] fix(timeline): skip empty-title events in the cluster lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A titleless or whitespace-only event stored `''` in the lookup, so its letters still clustered and rendered a label-less `✉` mystery card. The lookup now skips events whose trimmed title is empty — those letters stay loose. Fixes review finding #8. Refs #850 Co-Authored-By: Claude Opus 4.8 --- .../src/lib/timeline/eventClustering.spec.ts | 16 ++++++++++++++++ frontend/src/lib/timeline/eventClustering.ts | 8 ++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/timeline/eventClustering.spec.ts b/frontend/src/lib/timeline/eventClustering.spec.ts index f469f1c9..4b42fea9 100644 --- a/frontend/src/lib/timeline/eventClustering.spec.ts +++ b/frontend/src/lib/timeline/eventClustering.spec.ts @@ -59,6 +59,22 @@ describe('eventClustering — buildEventLookup', () => { expect(lookup.has(EV_A)).toBe(false); expect(lookup.size).toBe(0); }); + + it('skips an event with an empty or whitespace title — no bare ✉ card (#8)', () => { + const timeline: TimelineDTO = { + years: [ + { + year: 1916, + entries: [ + makeEvent({ eventId: EV_A, title: '' }), + makeEvent({ eventId: EV_B, title: ' ' }) + ] + } + ], + undated: [] + }; + expect(buildEventLookup(timeline).size).toBe(0); + }); }); describe('eventClustering — splitYearLetters', () => { diff --git a/frontend/src/lib/timeline/eventClustering.ts b/frontend/src/lib/timeline/eventClustering.ts index 22e3ee78..e80a43fd 100644 --- a/frontend/src/lib/timeline/eventClustering.ts +++ b/frontend/src/lib/timeline/eventClustering.ts @@ -33,13 +33,17 @@ export interface SplitLetters { * Only year-band events are collected: an undated event renders as a plain pill in the undated * bucket (out of clustering scope), so including it would scatter its dated letters into orphaned * cross-year cards detached from that pill (#7). + * + * An event with an empty/whitespace title is skipped too — clustering under it would render a + * label-less `✉` mystery card; its letters stay loose instead (#8). */ export function buildEventLookup(timeline: TimelineDTO): Map { const lookup = new Map(); const collect = (entries: TimelineEntryDTO[]) => { for (const entry of entries) { - if (entry.kind === 'EVENT' && entry.eventId && entry.type !== 'HISTORICAL') { - lookup.set(entry.eventId, entry.title ?? ''); + const title = entry.title?.trim(); + if (entry.kind === 'EVENT' && entry.eventId && entry.type !== 'HISTORICAL' && title) { + lookup.set(entry.eventId, title); } } };