Gantt
The Gantt engine on its own: tasks in, the rows on screen, the timeline they are measured against and the resolved arrows out, with no markup. It does the tree, the roll-up, the timeline and the dependency resolution — everything a Gantt is except the drawing.
This is a static class, not a Blazor component
Call
GanttBuilder.Build from C# when the plan is going somewhere that is not a Blazor page.
For a page, use BbGantt, which draws the same result as a table.
Positions Are Counted in Slots, Not in Days
Every minor slot is one unit wide whatever it holds, so a renderer multiplies by one slot width and is done.
OffsetSlots and LengthSlots are already in those units. The whole axis sits at one UTC offset, taken from the range start, so a daylight saving change inside the range shifts nothing — which is what keeps a day column exactly one day wide across the boundary.The Two Tiers of the Timeline
GanttTimeAxis.Build makes the headings on its own, with no tasks at all. Span on a major slot is the colspan a renderer needs without measuring anything. The minor tier is what positions are measured in; Year zoom has no tier above it, so Major comes back empty.A Date In, a Position Out — and Back Again
Position turns an instant into a fractional slot index and At turns one back into an instant. They are inverses, and they are what a drag needs: the browser reports how many slots a bar moved, and At turns that into the dates a caller can store. Both clamp to the charted range, and Position uses a binary search over the slot boundaries rather than walking them.A Task With Children Is a Summary
A summary takes its dates and its progress from its children. Progress is weighted by how long each child runs, so a fortnight half done outweighs an afternoon finished; a summary of nothing but milestones has no length to weigh, so they count equally.
RollUpSummaries = false turns the lot off, for a summary that carries a baseline agreed separately from the work beneath it.Dependencies Resolved Against the Rows on Screen
A
GanttLink is a dependency already resolved: which rows it joins, where along the timeline each end sits, and which end of each bar it is tied to. An arrow reaching a task whose row has been folded away is re-tied to the nearest ancestor still on screen; an arrow whose two ends fold into the same row is dropped rather than drawn as a loop. Fold the branches below and watch the count change.A dependency is drawn, not enforced
All four types are there, each named for the two ends it ties. Moving a task does not move what depends on it, because rescheduling a plan is a decision about float, calendars and who is free rather than something a chart should do behind the reader's back.