Skip to content

finding(plugin-timeline): a gantt timeline with an EMPTY literal items array throws — calculateDateRange reduces an empty list #6750

Description

@os-sales

Filed unassigned, found while implementing #6655 (the object-bound gantt refusal). Not the same defect and not fixed by that card — see "Why #6655 does not cover this" below.

What happens

calculateDateRange has no empty-list guard. With variant: 'gantt' and a literal but EMPTY items array, it reduces nothing:

constallDates=items.flatMap((row)=>(row.items||[]).flatMap((item)=>[item.startDate,item.endDate]));constminTimestamp=Math.min(...allDates.map((d)=>newDate(d).getTime()));

allDates is [], Math.min() over an empty list is Infinity, and new Date(Infinity).toISOString() throws.

Measured on 371dd804a (the #6655 branch head, which does not change this path), with a throwaway probe since no pin covers it:

AssertionError: PROBE renderer: expected RangeError: Invalid time value to be null
AssertionError: PROBE ObjectTimeline: expected RangeError: Invalid time value to be null
Tests 2 failed (2)

Both entry points crash identically:

  • TimelineRenderer given { type: 'timeline', variant: 'gantt', items: [] } directly;
  • ObjectTimeline given the same schema — an authored empty array is truthy, so effectiveItems returns it as authored items and hands it straight through.

Why this is reachable in practice

An empty gantt is a perfectly ordinary state, not a malformed document. Any author or generator that builds items from a collection produces items: [] the moment the collection is empty — a filtered project list with no matches, a fresh workspace, a plan whose rows are yet to be added. The current behaviour is a hard render crash for the empty case of an otherwise valid schema, where an empty grid or an empty-state panel is what every other surface in this repo does.

Why #6655 does not cover this

#6655 is the OBJECT-BOUND path composing the wrong item shape: it maps records to flat feed items, which have no nested items, so the gantt branch reads undefined on every row. Its ruling put an authoring refusal in ObjectTimeline, deliberately keyed on whether the items were AUTHORED so that legitimate literal gantt documents keep rendering.

This one is the opposite case — the items ARE authored, the shape IS the gantt row shape, and the document is legitimate. It is the crash site's own missing guard, on the path #6655 was explicitly told to leave untouched. Neither the refusal nor its pins fire here, and the empirical result above was taken with #6655's fix in the tree.

Where a fix would go

packages/plugin-timeline/src/renderer.tsxcalculateDateRange (the function starts at :165 and throws at :174 on 371dd804a), or its caller on the gantt branch at :424.

Two directions, both plausible, which is why this is filed rather than fixed:

  1. Guard in calculateDateRange — return a sentinel range for an empty list and let the gantt render an empty grid with a valid axis. Cheapest, and keeps schema.minDate / schema.maxDate meaningful (an author who pinned an explicit range arguably should get exactly that range with no rows in it, which this direction gives for free).
  2. Empty state on the gantt branch — render the repo's standard empty state instead of a zero-row chart, matching what the grid and kanban surfaces do. Better product behaviour, but it is a UI decision about what an empty gantt should look like, not a mechanical fix.

The same empty-list question also applies to the axis header row (generateTimeScaleHeaders) and to calculateBarDimensions, whose totalDuration is 0 for a degenerate range — worth deciding once for the whole branch rather than patching the one throw.

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatchedpriority:p1

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions