Skip to content

Connecting (chaining) tickets in project timeline - #44

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

wdembinski merged 15 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 15 commits September 10, 2026 18:47
Add SVG marker defs to TimelinePane's overlay (grey for the existing
`blocks` arrows, cyan defined for the chain arrows landing next) and
apply markerEnd to the dependency paths, matching ChainOverlay's own
Head marker pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TimelinePane now also loads chain:links and subscribes to chain:changed,
alongside its existing ticketLink:list/ticketLink:changed. Each task_link
between two scheduled rows is drawn as a cyan curve, reusing
ganttDependencyPath, the rowIndexById/scheduledRows lookup, and the cyan
arrowhead defined in the prior step. Render only — drag-to-reschedule is
untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each scheduled bar grows a small knob past its right edge, hidden until
the row is hovered, past the resize strip so the two gestures never
share a pixel. Dragging from it tracks the pointer as an offset from
the knob's own origin (no DOM measurement, same trick the reschedule
drag already uses) and draws a live rubber band with chainArrows'
rubberBandPath, the same function ChainOverlay draws its own band
with. Gesture and preview only — releasing creates no link yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
On release, a connect drag resolves its drop point back to the bar it
landed on (elementFromPoint against the row's data attribute, since a
pointer capture keeps events routed to the knob) and draws a `blocks`
dependency from the knob's own ticket to it. canLinkTickets runs the
same self/duplicate checks the store's handler will, surfacing a
refusal through the pane's existing dragError MessageBar without a
round trip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Holding Ctrl/Cmd at release now asks for both a blocks dependency and
an execution-chain edge (chain:link, gated after-merge) over the same
pair, not just the dependency. The two asks are independent, so a drop
can half land -- e.g. the dependency goes through but the chain drag
canLink refuses as a cycle -- and dragError now says which half of a
partial result failed and why, reusing the same MessageBar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each Gantt arrow now gets a transparent 14px hit-stroke over its thin
visible line, `ChainOverlay`'s own trick for turning a 1.5px line into
something worth clicking. A click selects it, drawn heavier in the
brand colour with a dot at each end so the two tickets it joins are
unambiguous — readying it for the delete/gate controls. Clicking a row
clears the selection, the chart's own version of "the board below
clears it on any click that reaches it".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Selecting a dependency or chain arrow in the timeline now surfaces a
popover: chain arrows reuse ChainLinkPopover (remove + gate picker,
wired to chain:unlink/chain:setGate), and dependency arrows get a new
TicketLinkPopover (remove only, via ticketLink:remove — a TicketLink
gates nothing). Delete/Backspace/Escape on the selected arrow mirror
the board's own keyboard contract.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@xyflow/react v12 joins react/react-dom/Fluent as a peerDependency of
@tm/ui, externalized in tsup's build so apps/web pulls its own copy
through dist rather than getting one bundled in. apps/client and
apps/web each install it directly so both hosts actually bundle it —
apps/client resolves packages/ui straight from src via its @ui/* alias
and never touches the tsup build.

Its stylesheet is imported from Projects.tsx, where the graph view
this unblocks will live as a third tab alongside Backlog and Timeline.
No feature code yet — both app builds and packages/ui's test suite
pass unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Projects.tsx's view switch grows a third 'graph' alongside backlog and
timeline, with its own ToggleButton. GraphPane.tsx mounts an empty
React Flow canvas — project-scoped, keyed on the project the same way
BacklogTable and TimelinePane are, with its own load-then-render shape
via useInitialLoad/PaneLoading. No nodes or edges yet; this stands up
the view and its navigation only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GraphPane now loads a project's tickets via board:tasks and stays live off
task:changed/project:tasksChanged, the same seed-and-subscribe shape
BacklogTable and TimelinePane already use. Each ticket renders as a
Fluent-styled React Flow node (key badge, title, type glyph, epic marker)
placed in a deterministic grid. No edges yet — persisted layout and links
come in later steps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
GraphPane now loads ticketLink:list and chain:links (staying live off their
change events, the same seed-and-subscribe shape it already uses for its own
tickets) and renders them as React Flow edges: grey for a blocks dependency,
cyan for an execution-chain link, each with a closed arrowhead. Read-only —
dragging to create or delete a link in the graph comes in later steps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wires React Flow's onConnect to GraphLinkPicker, a small dialog that
asks which kind of relationship a connect drag meant: a blocks
dependency (ticketLink:add) or an execution chain (chain:link, with
its own gate choice). Unlike the timeline's connect knob, a React Flow
drag has no ctrl-drag mid-gesture to also ask for a chain, so the
choice is made explicit in the dialog instead of inferred from a
modifier key. canLinkTickets/canLink run the same checks the store's
own handlers will, so a refusal it already knows about — self,
duplicate, a would-be cycle — reads inline without a round trip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wire React Flow's onEdgesDelete (edges are selectable/deletable by
default, so select + Delete/Backspace is the affordance) to
ticketLink:remove / chain:unlink. A deleted edge's id is resolved back
to its kind by checking the links list first then chainLinks, mirroring
TimelinePane's selectedTicketLink/selectedChainLink split.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a ticket_graph_positions table (projectId, taskId, x, y; cascading
from tasks/projects) plus Store.getTicketGraphLayout/saveTicketGraphLayout,
relayed as ticketGraph:getLayout/ticketGraph:saveLayout. GraphPane loads
saved positions alongside its tickets, grid-places any ticket without one,
and saves the whole current layout (debounced) on node drag so arrangements
survive a reload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tore

Adds scripts/verify-connections.mjs, headless verification (under
ELECTRON_RUN_AS_NODE, following the verify-tickets.mjs convention) that
addTicketLink/deleteTicketLink, addTaskLink/deleteTaskLink, and
getTicketGraphLayout/saveTicketGraphLayout round-trip correctly against a
real createStore instance: refusals, cascading deletes, the upsert-restamps-
projectId behaviour saveTicketGraphLayout's own doc comment describes, and
the cross-type-vs-cross-gate distinction between ticket_links and
task_links. Proven able to fail by mutating the layout upsert's ON CONFLICT
clause and confirming the two dependent checks went red, then restoring it.

Not covered here, and left for a human: the bar connect-drag, the Ctrl
modifier that switches a drag to a chain link, arrow select/delete, and
React-Flow canvas create/arrange in the Graph view — all pointer/DOM
interaction this headless script cannot drive.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@wdembinski
wdembinski force-pushed the feat/connecting-chaining-tickets-in-project branch from 5291d3e to f55f83e Compare September 10, 2026 16:50
@wdembinski
wdembinski merged commit c1dd22a 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