Skip to content

Connecting (chaining) tickets in project timeline - #45

Merged
wdembinski merged 7 commits into
developmentfrom
feat/connecting-chaining-tickets-in-project
Sep 10, 2026
Merged

wdembinski merged 7 commits into
developmentfrom
feat/connecting-chaining-tickets-in-project

Conversation

@wdembinski

Copy link
Copy Markdown
Owner

That feature just landed but I can't use it. Why?

wdembinski and others added 7 commits September 10, 2026 19:44
The knob sat ~4px past the bar's own right edge with nothing painted
in between, so a pointer crossing that gap rode over no hit-testable
descendant of the row's `<g>` — `:hover` dropped mid-crossing, hiding
the knob before the pointer landed on it.

Add a short transparent, pointerEvents:'auto' bridge rect spanning
from the bar's edge through the knob (the resize handles' own
fill:transparent trick), so the row stays hovered continuously from
bar to knob. Clicks on the bridge carry no handler of their own and
bubble to the row `<g>`, same as clicking the bar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GraphPane ran React Flow fully controlled with no onNodesChange, so
React Flow couldn't apply live drag deltas -- a dragged node only
moved once onNodeDragStop synced positions back through the nodes
memo. Switch to useNodesState/onNodesChange so drags render live,
while still reconciling tickets+positions into it (new/removed
tickets and refreshed ticket data), and keep the debounced
ticketGraph:saveLayout on drag stop. Also remove <Background /> so
the canvas no longer shows the dotted grid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An edge click resolves to its kind (links then chainLinks, the same
split handleEdgesDelete already used) and reopens GraphLinkPicker in an
edit mode: switch dependency <-> chain (a gated remove-then-add across
the two id spaces) or delete outright, or just retune a chain edge's
gate. The remove half of a switch, and an outright delete, run through
GraphPane's existing removeTicketLink/removeChainLink so failures
surface via the same deleteError MessageBar the Delete key already
uses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An epic now lays out as an epicZone group node instead of an ordinary
ticket card, with every ticket whose epicTaskId names it nested as a
child (parentId + extent:'parent') at a position relative to the
zone. The zone sizes to enclose its children (or a small empty band
for a childless epic) and keeps the deterministic grid fallback and
per-project layout persistence working for both group and child
nodes. Also drops the mislabelled "Epic" caption TicketNode printed
on children rather than the epic itself — dead now that an epic never
renders as a TicketNode at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Draw a shaded band behind an epic's row and its contiguous child rows
in the Gantt (ganttEpicBands, the label column's tint, and the chart's
own rect) - the Timeline's analogue of the graph view's epicZone
container node.

An epic's own bar now unions its dated children's spans whenever it
has any, collapsed or expanded alike (previously only when collapsed)
- without that, an undated epic with dated children would draw no row
at all in the expanded case, leaving its children ungrouped. GanttRow
carries the new barFromChildren flag so the timeline still knows when
a bar has nothing coherent to reschedule TO.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
groupTickets bucketed every ticket including epics, and an epic's own
epicTaskId is null just like a true orphan's — so it fell into
NO_EPIC_GROUP even though its children already promoted it to a
header, making it render twice. Skip epic rows when bucketing and seed
every epic's group header up front so a childless epic still gets an
(empty) header instead of vanishing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closes out the chaining-tickets fix round. RELEASE.md §1 (typecheck, test,
build) is green, forced with no cache. The pure-logic coverage the plan
named already existed for the Timeline/Backlog (ganttEpicBands in
ganttLayout.test.ts, the NO_EPIC_GROUP regression in backlogView.test.ts)
but not for the Graph view's layoutNodes, which stayed un-exported and
untested through Phase 4 — exports it and adds graphLayout.test.ts,
asserting an epic renders as an epicZone, a child gets parentId/extent set
to it, a saved position wins over the grid/stack fallback for a zone and
its children alike, and a zone the plan didn't cover (an epicTaskId naming
an epic outside the project) still falls back to a top-level card.

Also extends scripts/verify-connections.mjs with an epic+child
getTicketGraphLayout/saveTicketGraphLayout scenario, proving the store's
indifference to which taskId is an epic rather than assuming it — proven
able to fail by mutating the child's expected position and confirming the
script exits non-zero, then restored.

All six defects the human reported against the merged feature check out
against the code already on this branch: the timeline connect-knob bridge
rect, GraphPane's useNodesState-driven live drag, the removed dot-grid
Background, the onEdgeClick edit picker, both epicZone/ganttEpicBands
zones, and groupTickets skipping isEpic rows.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@wdembinski
wdembinski merged commit 33c5e07 into development Sep 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant