Found while implementing #6781 (the ruled gantt date type rule, PR #6905). Not repaired there: how a refused value is spelled is a separate adjudication from which values are refused, and the ruling named findUnusableGanttDate / isUnusable as the landing site. Filing it so the decision is taken deliberately rather than inherited.
What is wrong
spellGanttDateValue in packages/plugin-timeline/src/renderer.tsx fills the {{value}} hole of timeline.gantt.unusableRange.malformedDate — the alert whose entire job, per its own docblock, is "to name the value the author actually wrote". It quotes strings, spells undefined / null / symbol as themselves, and falls back to String(value) for everything else.
That fallback branch was written when nothing but [object Object]-shaped values could reach it. Two populations reach it now and both come out unreadable.
1. Already true on main today (independent of #6781). An array date is refused, and the diagnostic names nothing at all — String([]) is the empty string:
items: [{ label: 'R', items: [{ title: 'T', startDate: '2024-01-01', endDate: [] }] }]
renders, measured on fab4802e3:
Unusable gantt date range — items[0].items[0].endDate is , which is not a valid date. ...
The value vanishes from its own sentence. This is exactly the blur spellGanttDateValue's docblock says it quotes strings to avoid ("so an empty or space-padded value is visible rather than vanishing"), reached through a different branch.
2. New once #6781 lands. The type rule refuses arrays and toString-able objects that used to draw a chart, and String renders them as plausible date text:
| authored value | diagnostic reads |
|---|
['2024-01-01'] | endDate is 2024-01-01, which is not a valid date |
[0] | endDate is 0, which is not a valid date |
0n (bigint) | endDate is 0, which is not a valid date |
{} | endDate is [object Object], which is not a valid date |
The first three actively mislead: they name a text that is a valid date, or a number that is an accepted value, so the author is told their correct-looking value is invalid with no hint that the problem is the wrapper. endDate is 0 is the worst of them — 0 on its own is an accepted gantt date under the same ruling.
Why it is not obvious what the right answer is
At least three defensible spellings, and they trade off differently:
JSON.stringify for arrays and plain objects — ["2024-01-01"], [], {} — visibly a wrapper, and it removes the blank; but it throws on cycles and drops undefined members, so it needs its own totality argument, which is the property the current helper was deliberately built to have.- Name the type instead of the value —
an array, an object, a bigint — always readable, never misleading, but it stops naming what the author wrote, which is the helper's stated purpose. - Both: type plus a bounded rendering.
There is also the question of whether the sentence itself should change for a type fault ("is not a date" rather than "is not a valid date"), which would be a new i18n key across all ten locale packs — a different file surface (packages/i18n) than this one.
Suggested scope when picked up
packages/plugin-timeline/src/renderer.tsx (spellGanttDateValue) and its pins. Note the existing message is shared with the inverted-range diagnostic and with the undefined / null / quoted-string cases that #6759 and #6770 pinned — those spellings are load-bearing and must not move. Whichever spelling wins, it wants the same treatment #6781 got: recorded as a rule with its ground, not as another special case.
Generated by Claude Code
Found while implementing #6781 (the ruled gantt date type rule, PR #6905). Not repaired there: how a refused value is spelled is a separate adjudication from which values are refused, and the ruling named
findUnusableGanttDate/isUnusableas the landing site. Filing it so the decision is taken deliberately rather than inherited.What is wrong
spellGanttDateValueinpackages/plugin-timeline/src/renderer.tsxfills the{{value}}hole oftimeline.gantt.unusableRange.malformedDate— the alert whose entire job, per its own docblock, is "to name the value the author actually wrote". It quotes strings, spellsundefined/null/symbolas themselves, and falls back toString(value)for everything else.That fallback branch was written when nothing but
[object Object]-shaped values could reach it. Two populations reach it now and both come out unreadable.1. Already true on
maintoday (independent of #6781). An array date is refused, and the diagnostic names nothing at all —String([])is the empty string:renders, measured on
fab4802e3:The value vanishes from its own sentence. This is exactly the blur
spellGanttDateValue's docblock says it quotes strings to avoid ("so an empty or space-padded value is visible rather than vanishing"), reached through a different branch.2. New once #6781 lands. The type rule refuses arrays and
toString-able objects that used to draw a chart, andStringrenders them as plausible date text:['2024-01-01']endDate is 2024-01-01, which is not a valid date[0]endDate is 0, which is not a valid date0n(bigint)endDate is 0, which is not a valid date{}endDate is [object Object], which is not a valid dateThe first three actively mislead: they name a text that is a valid date, or a number that is an accepted value, so the author is told their correct-looking value is invalid with no hint that the problem is the wrapper.
endDate is 0is the worst of them —0on its own is an accepted gantt date under the same ruling.Why it is not obvious what the right answer is
At least three defensible spellings, and they trade off differently:
JSON.stringifyfor arrays and plain objects —["2024-01-01"],[],{}— visibly a wrapper, and it removes the blank; but it throws on cycles and dropsundefinedmembers, so it needs its own totality argument, which is the property the current helper was deliberately built to have.an array,an object,a bigint— always readable, never misleading, but it stops naming what the author wrote, which is the helper's stated purpose.There is also the question of whether the sentence itself should change for a type fault ("is not a date" rather than "is not a valid date"), which would be a new i18n key across all ten locale packs — a different file surface (
packages/i18n) than this one.Suggested scope when picked up
packages/plugin-timeline/src/renderer.tsx(spellGanttDateValue) and its pins. Note the existing message is shared with the inverted-range diagnostic and with theundefined/null/ quoted-string cases that #6759 and #6770 pinned — those spellings are load-bearing and must not move. Whichever spelling wins, it wants the same treatment #6781 got: recorded as a rule with its ground, not as another special case.Generated by Claude Code