File size: 1,390 Bytes
dbb1bf9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | /**
* #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 };
}
|