Found while implementing #7164. Not repaired there: the ruling named the ROW and row.items shapes (a row is an object; its items, when present, is an array) and the bars inside a row stay z.any() in the mirror. A bar that is not a bar is one level below that scope.
What is wrong
Measured in render through the real TimelineRenderer, on a67abdc88 (main) and on the #7164 branch at 8e1dc8274 — identical:
items: [{ label: 'R', items: [null] }]
-> NAMED Unusable gantt date range — items[0].items[0].startDate is undefined, which is not a valid date. …
The outcome is defined (no crash — findUnusableGanttDate reads rowItems[0]?.startDate, gets undefined, and refuses), but the copy names the WRONG fault: the bar is null, not an item whose startDate is missing. This is the same mis-naming class #7164 fixed for rows ("items[0] is null, which is not a valid date" named the wrong fault for a row that is not a row), one level down.
And validate accepts it: TimelineSchema.items[].items is z.array(z.any()), so a null bar is green at authoring time.
Options
Recommendation: at least B — an authored null bar is an error an AI-authored document should be told about at validate, not by a date diagnostic naming a key it never wrote.
Blocked-by: #7164 (the mirror's row declaration and classifyGanttRows both land there).
Found while implementing #7164. Not repaired there: the ruling named the ROW and
row.itemsshapes (a row is an object; itsitems, when present, is an array) and the bars inside a row stayz.any()in the mirror. A bar that is not a bar is one level below that scope.What is wrong
Measured in render through the real
TimelineRenderer, ona67abdc88(main) and on the #7164 branch at8e1dc8274— identical:The outcome is defined (no crash —
findUnusableGanttDatereadsrowItems[0]?.startDate, getsundefined, and refuses), but the copy names the WRONG fault: the bar isnull, not an item whosestartDateis missing. This is the same mis-naming class #7164 fixed for rows ("items[0] is null, which is not a valid date" named the wrong fault for a row that is not a row), one level down.And
validateaccepts it:TimelineSchema.items[].itemsisz.array(z.any()), so anullbar is green at authoring time.Options
null, or whoseitemsis a truthy non-array, crashes the render —findUnusableGanttDatereads the walk defensively andcalculateDateRangereads it bare #7164 did for rows: the mirror declares a bar as an object (z.array(z.object({}).passthrough())forrow.items— a contract change, Clause ②), andclassifyGanttRowsnamesitems[0].items[0] is nullat render-time. The existingmalformedRowcopy says "which is not a row shape", which reads slightly off for a bar; either a fourth path level in the same sentence or a small copy adjustment across the ten packs would be needed, and that is a ruling.nullbar at authoring; leave the render-time copy as the defined outcome it is today.Recommendation: at least B — an authored
nullbar is an error an AI-authored document should be told about atvalidate, not by a date diagnostic naming a key it never wrote.Blocked-by: #7164 (the mirror's row declaration and
classifyGanttRowsboth land there).