All posts
Engineering · 7 min read

Building a Gantt view that feels alive

Dependency arrows that reroute while you drag, rescheduling by grabbing a bar — engineering notes from StrideP's timeline view.

Most web Gantt charts are a picture of a plan. We wanted StrideP's timeline to be the plan — something you grab and change, where the consequences show up while your finger is still down. Two features carried that feeling: dependency arrows and drag-to-reschedule. Both are simpler than they look, and both were almost entirely a geometry problem.

Arrows are derived, never stored

Every task bar's position comes from one pass that lays out groups and rows with fixed metrics — column width, row height, label gutter — and records each bar's rectangle in a map. The dependency arrows are an SVG overlay computed from that map: for each blocker→blocked edge, an elbow path from the end of one bar to the start of the other.

Because arrows are derived from bar geometry rather than stored, there is nothing to keep in sync. Move a bar and every arrow touching it is simply recomputed on the next render. A dependency that points backwards in time — a task scheduled to start before its blocker ends — flips the arrow to red. That single color change turns the chart from decoration into an early-warning system.

Dragging without lying

Rescheduling supports three grabs: the bar's middle moves the whole task, either edge adjusts just the start or due date. During the drag, the moved bar lives in a local overrides layer — the UI shows the proposed schedule instantly, arrows rerouting live, while the real data stays untouched. On release, the change commits through the same validated update path the task dialog uses, and the overrides layer clears when fresh data arrives.

Two small bugs taught us the classic lessons. Formatting a dragged date with UTC-based ISO conversion shifted it by a day for anyone east of Greenwich — dates that mean "a day on the calendar" must be formatted in local time. And a drag ends with a click the browser is happy to deliver to the bar underneath, which opened the task dialog after every drag until we learned to swallow it.