Repository navigation
Description
Summary
The aggregator's roadmap date parsing accepts impossible calendar dates. laneFor's date regex (pm-stack/aggregator/src/derive.ts:65, DATE = /^(\d{4})-(\d{2})-\d{2}/) and rowDate's identical regex (derive.ts:459, YMD) check only the shape of a Horizon cell — two digits for the month — never that the month/day exist. A Grist "Program roadmap" Horizon typo such as 2026-13-15 or 2026-00-15 therefore does not fall into the "other / unscheduled" lane the code reserves for unrecognized horizons (laneFor, derive.ts:71-82); it is treated as a real date and produces nonsense lanes and labels, and one label helper throws on it:
laneFor("2026-13-15")→{ key: "2026-Q5", label: "Q5 2026", order: 8109 }— a fifth quarter, sorting exactly where Q1 2027 sorts.laneFor("2026-00-15")→{ key: "2026-Q0", label: "Q0 2026", order: 8104 }.monthTimelineon a2026-13-15row → lane{ key: "2026-13", label: "undefined 2026" }— the literal text "undefined 2026" is what the month-grain roadmap page renders (render.ts:1514callstimelineTable(monthTimeline(rows), …);monthLabel,derive.ts:504-507, indexesMONTH_NAMES[m - 1]unchecked).ymdLabel("2026-13-15")throwsTypeError: undefined is not an object (evaluating 'MONTH_NAMES[d.getUTCMonth()].slice')(derive.ts:499-502) — an invalidDateyieldsNaNmonth/day indexes. Any present or future caller that labels a row-derived date (rather than a generated lane day) crashes on this input.ymdLabel("2026-02-30")silently relabels the impossible date as "Mon Mar 2" (JS Date roll-over), androadmapDayGraincounts a2026-02-30/2026-13-15row as "dated, outside this window" (outside = 1, coarse = 0) instead of as an unparseable/coarse row, so the roadmap's own accounting misstates it.
Verified (this run, on main be7cf62)
Probe ON THE LAPTOP (Bun 1.4.2, driving the real derive.ts — sha256 312ac0fb… identical in both clones; probe + log in the goal's hidden_files/probe-2026-10-28-laneB-horizondate/, run twice with identical results):
CTRL laneFor 2026-10-24: {"key":"2026-Q4","label":"Q4 2026","order":8108}
CTRL laneFor Q1 2027: {"key":"2027-Q1","label":"Q1 2027","order":8109}
CTRL monthLabel 2026-10: October 2026
CTRL ymdLabel 2026-10-24: Sat Oct 24
BUG laneFor 2026-13-15: {"key":"2026-Q5","label":"Q5 2026","order":8109}
BUG laneFor 2026-00-15: {"key":"2026-Q0","label":"Q0 2026","order":8104}
BUG monthLabel 2026-13: undefined 2026
BUG monthLabel 2026-00: undefined 2026
BUG rowDate 2026-13-15: 2026-13-15 (accepted as a date)
BUG rowDate 2026-02-30: 2026-02-30 (accepted as a date)
BUG ymdLabel 2026-02-30: Mon Mar 2 (silently a different date)
BUG ymdLabel 2026-13-15: TypeError … MONTH_NAMES[d.getUTCMonth()].slice (throws)
BUG monthTimeline lanes for 2026-13-15: [{"key":"2026-13","label":"undefined 2026","order":24325}]
BUG dayGrain 2026-02-30: outside=1 coarse=0 laned=0
Existing tests cover only valid horizons (test/render.test.ts:127-129: Q1 2027, 2026-10-24, Ongoing).
Reachability: Horizon is a free-text Grist cell, edited by humans and fleet jobs; the roadmap views are rendered from whatever it contains. One mistyped month (2026-13-… for a December/January slip, or a 00 from a spreadsheet export) is enough to put a phantom Q5/Q0 lane and an "undefined 2026" heading on the live roadmap, mis-sort the row into a neighbouring quarter, and count it as a real dated row outside the day-grain window.
Why it matters
The roadmap is the Program view's backbone: quarter/month/day grains all derive from these same helpers, so one bad cell corrupts every grain differently (phantom quarter, undefined month heading, miscounted day window) instead of degrading once, visibly, into the existing "other/coarse" bucket. The helpers' own contract already anticipates unrecognized horizons — laneFor sorts "anything else" after every quarter — the date branch simply never validates that its digits form a date, so the fallback never fires for the most likely typo class.
Acceptance criteria
- A Horizon whose digits do not form a real calendar date (month
00/13, day00, Feb 30, Apr 31, …) is treated as unrecognized:laneForreturns the "other" lane for it androwDatereturnsnull, so the row lands in the coarse/other bucket in every grain. - No roadmap surface can render the literal
undefinedas a lane/month label, andymdLabel/monthLabelcannot throw or silently relabel an impossible date (defensive behaviour for direct callers is pinned by a test). - Regression tests cover at least:
2026-13-15,2026-00-15,2026-02-30throughlaneFor,rowDate,monthTimeline, androadmapDayGrain(counted coarse, not "outside"), plus the existing valid controls (2026-10-24→ Q4 2026, a real Feb 29 on a leap year). - Existing aggregator test suites still pass.
Activity
developing-today commentedon Oct 28, 2026
CODE-649
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
11 remaining items
dezren39 commentedon Oct 28, 2026
🤖 Agent note · picked up
style(aggregator): biome-format the #1628 regression tests (treefmt)
Merge pull request #1683 from developing-today/queue/1896-1628-horizo…
dezren39 commentedon Oct 28, 2026
🤖 Agent note · done
bun test test/render.test.ts24 pass (6 new aggregator: roadmap Horizon dates are never validated — month 13 invents a Q5 lane and an 'undefined 2026' month, month 00 a Q0 lane, and ymdLabel throws on them #1628 regression tests); CI green (run, treefmt after a biome-format fixup, Nix flake evaluation). One pre-existing failure in the full aggregator suite (actions.test.tsapplied-snooze strip) reproduces identically on clean main and is filed as aggregator: actions.test.ts fails on main — applied snooze never lands in the Snoozed strip ('Snoozed by you (1)' missing) #1682 — untouched by this change.