Found while implementing #6907 (the gantt-date spelling rule, PR #7026). Not repaired there: it sits in findUnusableGanttDate's type gate, one function upstream of the speller that card is about, and it is #6781's surface rather than #6907's.
What is wrong
isGanttDateType uses value instanceof Date as the repo idiom for "is a Date". An object that inherits from Date.prototype without owning a [[DateValue]] internal slot passes that gate, and then new Date(value) in isUnusable runs ToPrimitive on it and throws:
Object.create(Date.prototype) instanceof Date -> true (passes the type gate)
new Date(Object.create(Date.prototype)).getTime() -> THREW TypeError:
Method Date.prototype.toString called on incompatible receiver [object Object]
Measured on b458300ca with a throwaway node probe. The crash is uncaught and mid-render, so the author gets a blank screen where a named diagnostic belongs — the exact failure mode #6781 set out to remove when it put the type gate before new Date, and the reason spellGanttDateValue was written total by construction.
So #6905's totality argument for findUnusableGanttDate — "new Date is only ever reached with a string, a finite number, or a Date, none of which throw" — has one gap: instanceof Date admits a Date-shaped object that is not a Date.
Why it was left alone rather than fixed in #6907
Judged not reachable from an authored document, which is why this is filed as an observation rather than repaired inline:
- ObjectUI metadata is JSON, and JSON cannot express
Object.create(Date.prototype).
- Data adapters produce real
Dates (via new Date(...)), plain objects, strings or numbers — all of which own their slots or are refused cleanly.
It needs hand-written JS that deliberately constructs a slot-less Date impostor. Recording it because #6781's totality property is stated out loud in the code, and a stated invariant with a known gap should be either closed or documented rather than quietly untrue.
Suggested scope if picked up
packages/plugin-timeline/src/renderer.tsx, isGanttDateType / isUnusable. Two routes, both cheap:
- Tighten the gate so it tests the slot, not just the prototype chain — e.g. judge
Number.isFinite(value.getTime()) behind a total guard, or use the Object.prototype.toString.call(value) === '[object Date]' brand test. Note that brand test consults Symbol.toStringTag, which can itself be a throwing getter, so it is not free.
- Decide the invariant is "reachable inputs only" and say so in the docblock, narrowing the totality claim to the values an authored document can carry.
Route 2 may well be the right answer under startup scope discipline; the point of this card is that the choice should be made rather than inherited. Whichever wins, spellGanttDateValue itself is unaffected — after #6907 it is total over every value that reaches it.
Refs: #6907 / PR #7026 (the spelling rule) - #6781 / PR #6905 (the type rule and its totality argument) - #6759 / #6770 (the pinned spellings)
Generated by Claude Code
Found while implementing #6907 (the gantt-date spelling rule, PR #7026). Not repaired there: it sits in
findUnusableGanttDate's type gate, one function upstream of the speller that card is about, and it is #6781's surface rather than #6907's.What is wrong
isGanttDateTypeusesvalue instanceof Dateas the repo idiom for "is a Date". An object that inherits fromDate.prototypewithout owning a[[DateValue]]internal slot passes that gate, and thennew Date(value)inisUnusableruns ToPrimitive on it and throws:Measured on
b458300cawith a throwaway node probe. The crash is uncaught and mid-render, so the author gets a blank screen where a named diagnostic belongs — the exact failure mode #6781 set out to remove when it put the type gate beforenew Date, and the reasonspellGanttDateValuewas written total by construction.So #6905's totality argument for
findUnusableGanttDate— "new Dateis only ever reached with a string, a finite number, or aDate, none of which throw" — has one gap:instanceof Dateadmits a Date-shaped object that is not a Date.Why it was left alone rather than fixed in #6907
Judged not reachable from an authored document, which is why this is filed as an observation rather than repaired inline:
Object.create(Date.prototype).Dates (vianew Date(...)), plain objects, strings or numbers — all of which own their slots or are refused cleanly.It needs hand-written JS that deliberately constructs a slot-less Date impostor. Recording it because #6781's totality property is stated out loud in the code, and a stated invariant with a known gap should be either closed or documented rather than quietly untrue.
Suggested scope if picked up
packages/plugin-timeline/src/renderer.tsx,isGanttDateType/isUnusable. Two routes, both cheap:Number.isFinite(value.getTime())behind a total guard, or use theObject.prototype.toString.call(value) === '[object Date]'brand test. Note that brand test consultsSymbol.toStringTag, which can itself be a throwing getter, so it is not free.Route 2 may well be the right answer under startup scope discipline; the point of this card is that the choice should be made rather than inherited. Whichever wins,
spellGanttDateValueitself is unaffected — after #6907 it is total over every value that reaches it.Refs: #6907 / PR #7026 (the spelling rule) - #6781 / PR #6905 (the type rule and its totality argument) - #6759 / #6770 (the pinned spellings)
Generated by Claude Code