Description
A task that has no computed layout position still renders a wx-bar element. The result is a "ghost bar" pinned at the chart origin whose apparent duration is the pixel width of its own label — it reads as a real (and very wrong) schedule.
Observed in @svar-ui/react-gantt 2.7.0 (MIT).
Repro
Add a dateless task to any demo dataset, e.g. in demos/data.js:
{ id: 999, text: 'Dateless task (ghost bar repro)', parent: 0 },
A bar appears at the chart start spanning roughly six "days" — exactly the width of the label text. A longer title produces a "longer schedule".
Mechanism
- Date normalization is skipped for the task — either it carries
unscheduled: true, or it simply has no start (calcDates is a no-op without a start date).
- The layout math therefore leaves
$x/$w as NaN.
taskStyle() in src/components/chart/Bars.jsx emits left: "NaNpx", width: "NaNpx"; the browser rejects the invalid declarations.
- The bar renders with only
top/height → positioned at the origin, auto-sized by its content.
The existing skip guard doesn't catch this case: task.$skip && task.$skip_baseline — $skip is only set when the unscheduledTasks feature is enabled, and $skip_baseline stays undefined unless the baseline pass runs.
This is particularly easy to hit from the MIT package: the store's init() disables unscheduledTasks, but normalizeDates still honors the per-task unscheduled flag — so any dateless task always paints a ghost bar, with no way to opt out.
Expected
A task without real coordinates should keep its grid row but paint nothing on the timeline.
Fix
PR #29 guards the main bar and the baseline element on Number.isFinite() of their computed positions (!= null is insufficient since the value is NaN, not undefined). Verified against the demos: the repro row stays in the grid with an empty timeline, and all scheduled bars/milestones/summaries/baselines render unchanged.
🤖 Generated with Claude Code
Description
A task that has no computed layout position still renders a
wx-barelement. The result is a "ghost bar" pinned at the chart origin whose apparent duration is the pixel width of its own label — it reads as a real (and very wrong) schedule.Observed in
@svar-ui/react-gantt2.7.0 (MIT).Repro
Add a dateless task to any demo dataset, e.g. in
demos/data.js:A bar appears at the chart start spanning roughly six "days" — exactly the width of the label text. A longer title produces a "longer schedule".
Mechanism
unscheduled: true, or it simply has nostart(calcDatesis a no-op without a start date).$x/$wasNaN.taskStyle()insrc/components/chart/Bars.jsxemitsleft: "NaNpx",width: "NaNpx"; the browser rejects the invalid declarations.top/height→ positioned at the origin, auto-sized by its content.The existing skip guard doesn't catch this case:
task.$skip && task.$skip_baseline—$skipis only set when theunscheduledTasksfeature is enabled, and$skip_baselinestaysundefinedunless the baseline pass runs.This is particularly easy to hit from the MIT package: the store's
init()disablesunscheduledTasks, butnormalizeDatesstill honors the per-taskunscheduledflag — so any dateless task always paints a ghost bar, with no way to opt out.Expected
A task without real coordinates should keep its grid row but paint nothing on the timeline.
Fix
PR #29 guards the main bar and the baseline element on
Number.isFinite()of their computed positions (!= nullis insufficient since the value isNaN, notundefined). Verified against the demos: the repro row stays in the grid with an empty timeline, and all scheduled bars/milestones/summaries/baselines render unchanged.🤖 Generated with Claude Code