refactor(timeline): O(1) lookups in YearBand row assembly
`loose.includes(entry)` ran once per LETTER inside the band loop — O(L²) on a dense band of hundreds of loose letters, recomputed on every layer re-render. splitYearLetters now also returns its `byEvent` map, so a letter's disposition is `byEvent.has(linkedEventId)` and an event's card is `byEvent.get(eventId)`, both O(1); `consumed` is a plain object. No behavior change. Fixes review finding #3. Refs #850 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -54,11 +54,15 @@ const split = $derived(
|
||||
);
|
||||
const loose = $derived(split.loose);
|
||||
const dense = $derived(isDense(loose.length));
|
||||
// Clusters keyed by eventId (built once in splitYearLetters): row assembly looks a letter's
|
||||
// disposition up in O(1) — `byEvent.has(linkedEventId)` — instead of scanning the loose array
|
||||
// per letter (was O(L²) on a dense band), and resolves an event's card with `byEvent.get`.
|
||||
const byEvent = $derived(split.byEvent);
|
||||
|
||||
const rows = $derived.by<Row[]>(() => {
|
||||
const out: Row[] = [];
|
||||
const { clusters } = split;
|
||||
const consumed: string[] = [];
|
||||
const consumed: Record<string, true> = {};
|
||||
let stripInserted = false;
|
||||
let letterIndex = 0;
|
||||
|
||||
@@ -67,16 +71,19 @@ const rows = $derived.by<Row[]>(() => {
|
||||
// A curated event whose letters live in THIS band becomes the contained card's
|
||||
// header — its title reads once, no separate pill (REQ-002). Otherwise it stays a
|
||||
// plain pill/world-band (REQ-005).
|
||||
const cluster = entry.eventId ? clusters.find((c) => c.eventId === entry.eventId) : undefined;
|
||||
const cluster = entry.eventId ? byEvent.get(entry.eventId) : undefined;
|
||||
if (cluster) {
|
||||
out.push({ t: 'eventcard', event: entry, cluster });
|
||||
consumed.push(cluster.eventId);
|
||||
consumed[cluster.eventId] = true;
|
||||
} else {
|
||||
out.push({ t: 'event', entry });
|
||||
}
|
||||
} else if (loose.includes(entry)) {
|
||||
// A loose letter: alternate while sparse, or fold the whole loose set into one
|
||||
// density strip (inserted once, at the first loose letter) when dense.
|
||||
} else if (
|
||||
entry.kind === 'LETTER' &&
|
||||
!(entry.linkedEventId && byEvent.has(entry.linkedEventId))
|
||||
) {
|
||||
// A loose letter (not clustered): alternate while sparse, or fold the whole loose set
|
||||
// into one density strip (inserted once, at the first loose letter) when dense.
|
||||
if (!dense) {
|
||||
out.push({ t: 'letter', entry, side: letterIndex % 2 === 0 ? 'left' : 'right' });
|
||||
letterIndex += 1;
|
||||
@@ -91,7 +98,7 @@ const rows = $derived.by<Row[]>(() => {
|
||||
// Cross-year clusters: a cluster whose event is NOT a same-year EVENT entry renders as a
|
||||
// text-header card (no pill, no edit link) holding this year's linked letters (REQ-004).
|
||||
for (const cluster of clusters) {
|
||||
if (!consumed.includes(cluster.eventId)) {
|
||||
if (!consumed[cluster.eventId]) {
|
||||
out.push({ t: 'eventcard', cluster });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user