/** * #4923: pure "new since your last visit" partition, extracted from * NewsPanel's first render so the behavior is unit-testable (review * finding on PR #4926 — a source-regex test cannot catch broken wiring). * * SEMANTIC DECISION (explicit, revisit deliberately): a cluster counts as * new-since-away when its `firstSeen` — the EARLIEST article in the * cluster — postdates the previous visit. Genuinely new stories flag NEW; * a long-running story that merely gained an update while away does NOT * (using lastUpdated would keep rolling stories perpetually NEW on every * return, which trains users to ignore the signal). * * prevVisitAt <= 0 means "no known previous visit" — everything is seen, * matching the pre-#4923 first-render behavior. */ export interface NewSinceVisitCluster { id: string; firstSeen: Date | string | number; } export function computeNewSinceVisit( clusters: readonly NewSinceVisitCluster[], prevVisitAt: number, ): { newIds: string[]; seenIds: string[] } { const newIds: string[] = []; const seenIds: string[] = []; for (const cluster of clusters) { const firstSeenMs = new Date(cluster.firstSeen).getTime(); if (prevVisitAt > 0 && Number.isFinite(firstSeenMs) && firstSeenMs > prevVisitAt) { newIds.push(cluster.id); } else { seenIds.push(cluster.id); } } return { newIds, seenIds }; }