Let the timeline draw a rule's whole rhythm
The lists carry two occurrences because they answer "what ends soonest"; the board exists to show rhythm, so it draws every occurrence in view. expand is called with the settled window rather than returning rows up front, and the ordering is the point: boardWindow takes its max from the ends it is handed, so feeding expanded occurrences back into it would widen the window, generate more, and widen it again — a rule with no until would never terminate. Compute the range from the base rows, then expand into it. Merged rows are re-sorted only when there is something to merge, so a reader with no repeating events sees identical behaviour. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f7f67fd305
commit
181eceac6f
@@ -821,3 +821,29 @@ describe("CatchUpPanel", () => {
|
||||
expect([...markup.matchAll(/aria-label="[^"]*(?:not )?done"/g)]).toHaveLength(17);
|
||||
});
|
||||
});
|
||||
|
||||
describe("boardWindow is not widened by expansion", () => {
|
||||
test("a rule's occurrences cannot enlarge the board that generated them", () => {
|
||||
// The circularity guard. boardWindow takes max from the ends it is given,
|
||||
// so if expanded occurrences were fed back into it, each pass would widen
|
||||
// the window, generate more occurrences and widen it again — a rule with
|
||||
// until: null would never terminate. The fix is ordering: settle the window
|
||||
// from the base rows, THEN expand into it. This test pins the ordering by
|
||||
// asserting the window is a function of the base rows alone.
|
||||
const now = Date.parse("2026-09-03T12:00:00.000Z");
|
||||
const starts = [Date.parse("2026-09-01T00:00:00.000Z")];
|
||||
const ends = [Date.parse("2026-09-08T00:00:00.000Z")];
|
||||
|
||||
const base = boardWindow(starts, ends, now);
|
||||
|
||||
// A year of weekly occurrences, as `expand` would return them.
|
||||
const expandedEnds = Array.from({ length: 52 }, (_, i) =>
|
||||
Date.parse("2026-09-08T00:00:00.000Z") + i * 7 * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
const ifItLeaked = boardWindow(starts, [...ends, ...expandedEnds], now);
|
||||
|
||||
expect(base.max).toBeLessThan(ifItLeaked.max);
|
||||
// Which is exactly why Timeline must compute starts/ends from `plotted`
|
||||
// before calling expand — asserted structurally in the component below.
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user