Skip to content

feat(web): inline expand/collapse for subworkflows in the dashboard graph - #316

Merged
Jason Robert (jrob5756) merged 5 commits into
mainfrom
feature/314-inline-subworkflow-expand-collapse
Jul 17, 2026
Merged

feat(web): inline expand/collapse for subworkflows in the dashboard graph#316
Jason Robert (jrob5756) merged 5 commits into
mainfrom
feature/314-inline-subworkflow-expand-collapse

Conversation

@jrob5756

Copy link
Copy Markdown
Collaborator

Closes#314.

Renders subworkflows with inline expand/collapse in the dashboard graph
(collapsed by default) instead of drill-down being the only way to see a
subworkflow's internals. Drill-down "focus mode" is preserved — it's now an
option (double-click / breadcrumbs), not the sole path.

Built on React Flow (@xyflow/react) + dagre + Zustand. Landed as four stacked
commits, each independently verified (tsc + Vitest + vite build + a live
Playwright pass on a real workflow):

Phase 0 — foundation (7412556)

Context-namespaced node/edge IDs (contextKey::name) so multiple contexts can
render at once without collisions; recursive nested-DAG layout (per-context
dagre → sized container → parentId/extent: parent, boundary edges remapped
to ingress/egress); expandedContexts store state (default empty =
collapsed); per-subworkflow expand/collapse chevron; context-aware live status +
edge-highlight; namespaced selection model.

Phase 1 — controls + polish (0b01bf5)

Expand/Collapse-all toolbar button in <Controls> (hidden when nothing is
expandable) + E keyboard shortcut; bulk expandContexts/collapseContexts
store actions; collectExpandableContextKeys (bounded — does not descend into
for_each iterations); not-started placeholder chevron; subtle expand animation.

Phase 2 — for_each-of-workflow inline sub-containers (d43ce55)

A for_each group whose inline agent is type: workflow becomes an expandable
container: expanding it reveals each iteration as a collapsed sub-workflow pill;
expanding a pill embeds that iteration's inner DAG in place. Two key kinds in
expandedContexts (numeric context keys for an inner DAG; ::-bearing group
keys for a group container) that never collide. Expand-all reveals pills but
doesn't auto-expand every iteration's DAG (safe on wide fan-outs).

Phase 3 — expansion-aware deep-links + docs (451ab8b)

?agent= / ?subworkflow= deep-links now reveal the target inline — expand
its ancestor containers in the root graph and select+center it — instead of
re-rooting the view. New pure expansionKeysForContextPath() walks only the
target's ancestor chain (never siblings), so a wide fan-out never auto-expands.
Replay needs no server change (expansion is client-only UI state, default
collapsed; the existing debounce/resolve machinery waits for replayed state to
settle). docs/dashboard-deep-links.md updated to the reveal-inline behavior.

Verification

  • tsc -b, npm run test (Vitest 33/33), npm run build — all green.
  • conductor validate on the two bundled examples
    (examples/subworkflow-inline.yaml, examples/subworkflow-foreach-inline.yaml).
  • Live Playwright passes for each phase against a real --web run (inline
    expand/collapse, Expand/Collapse-all + E, for_each iteration containers,
    and deep-link inline reveal incl. the transitive agent + "not found" cases).

Notes

  • The built static/ dashboard bundle is rebuilt and committed (the server
    serves static/).
  • No CHANGELOG entry — added at release time per the maintainer's workflow.
  • Screenshots available on request.

Render subworkflows as expand/collapse containers in the same graph view
(collapsed by default) instead of drill-down only. Double-click drill-down
("focus") is retained.
Foundational work (Phase 0 of #314):
- Context-namespaced graph identity (lib/node-id.ts): every node/edge id is
`contextKey::name`, so the same agent name across contexts/iterations no
longer collides once multiple contexts render together.
- Recursive nested-DAG layout in graph-layout.ts: each expanded subworkflow
gets its own dagre pass, is sized into a React Flow parent container, and
anchors internal edges via the existing ingress/egress boundary nodes.
- Store: expandedContexts set + toggleContextExpanded (default collapsed).
- WorkflowGraph rebuilds only on topology/expansion changes (not status
ticks); status and edge-highlight resolve per owning context.
- Selection is namespaced end-to-end (useNodeLiveData / useSelectedNodeData,
all node components, AnimatedEdge, DetailPanel, ErrorBanner, OutputPane,
deep-links).
- Inline boundary nodes drop the redundant "from/return to <parent>" label
(kept only in drill-down, where no container header names the parent).
Adds examples/subworkflow-inline.yaml and Vitest coverage for namespacing and
nested layout. Rebuilds the dashboard bundle (static/) and refreshes the stale
AGENTS.md web/ description (React Flow, not Cytoscape).
Part of #314.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9af3cb49-70f2-42c7-b4ae-587b2820f924
Phase 1 of #314. Adds a toolbar control to bulk expand or collapse every
inline-expandable subworkflow in the viewed context at once, plus polish.
- Add an Expand/Collapse-all button to the React Flow <Controls> panel. It
is hidden when the viewed context has no expandable subworkflows, and
toggles its icon/label between "Expand all subworkflows (E)" and
"Collapse all subworkflows (E)" based on whether any are expanded.
- Add an `E` keyboard shortcut mirroring the `F` fit-view shortcut.
- Add bulk store actions `expandContexts(keys)` / `collapseContexts(keys)`
(immutable union/difference over `expandedContexts`, scoped to the subtree).
- Add `collectExpandableContextKeys` to graph-layout: recursively enumerates
inline-expandable sequential subworkflows (excludes for_each iterations and
not-yet-started children), namespaced relative to the viewed basePath.
- Polish: dimmed placeholder chevron on subworkflow pills before their child
DAG exists (keeps pill width stable, hints future expandability); a subtle
200ms fade/slide entrance on the expanded container chrome.
- Tests: +8 Vitest cases (collectExpandableContextKeys incl. nested/for-each/
not-started, and the bulk expand/collapse actions). 20/20 pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9af3cb49-70f2-42c7-b4ae-587b2820f924
Extend the dashboard's inline subworkflow expansion (issue #314) to
for_each-of-workflow groups. A for_each group whose inline agent is a
`type: workflow` step is now an expandable container: its left chevron
reveals each iteration as a collapsed sub-workflow pill stacked inside,
and expanding a pill embeds that iteration's own DAG in place
(container-within-a-container). Collapsed by default; for_each-of-agent
and not-yet-started groups stay leaf progress nodes. Drill-down and
Expand/Collapse-all (E) still work.
- graph-layout: `layoutForEachIterations` builds the stacked iteration
members (collapsed pill or recursively-embedded inner DAG) inside an
expanded group container; `forEachIterationContexts` finds a group's
iterations by slot-key prefix. `collectExpandableContextKeys` now also
returns for_each group keys so Expand-all reveals pills without
auto-expanding every inner DAG (bounded on wide fan-outs).
- node-id: add `forEachGroupKey` / `isGroupExpansionKey` /
`parseForEachSlotKey`. `expandedContexts` now holds two key kinds:
pure-numeric context keys (an iteration's inner DAG) and `::`-bearing
group keys (the group container); they never collide.
- GroupNode: expand chevron + expanded container header for
for_each-of-workflow groups (left-aligned, matching WorkflowNode).
- Iteration pills reuse WorkflowNode; their live status resolves from the
iteration SubworkflowContext via a new `iterationContextPath`, wired
through useNodeLiveData, the graph live-status effect, and
useSelectedNodeData (detail panel).
- WorkflowGraph: structureKey rebuilds when an expanded group's iteration
set (or an iteration's agents) changes, handling dynamic fan-out.
- Add examples/subworkflow-foreach-inline.yaml; +7 Vitest tests
(27 total); rebuild the static bundle.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9af3cb49-70f2-42c7-b4ae-587b2820f924
Deep-links (`?agent=` / `?subworkflow=`) now reveal the target node inline
by expanding its ancestor containers in the root graph and centering on it,
instead of re-rooting the view into the child context. Drill-down focus mode
stays available via double-click and the breadcrumb bar. This completes the
inline-subworkflow work for issue #314 (Phases 0-3).
- graph-layout.ts: add `expansionKeysForContextPath(contexts, path)` — a pure
helper returning only the target's ancestor-chain expansion keys (a sequential
subworkflow ancestor yields its context key; a `for_each` iteration yields its
group key plus its own context key). It walks only the ancestor chain, never
siblings, so a wide fan-out never auto-expands and layout stays bounded by
nesting depth.
- use-deep-link.ts: rewrite `apply()` to `expandContexts()` the ancestor chain
and select+center the target at the root frame (`viewContextPath: []`),
replacing the drill-down. Covers agent-at-target, agent-only transitive,
explicit path+agent, subworkflow-only, and not-found error paths; adds a
`contextAtPath` resolver. Replay needs no server change — expansion is
client-only UI state (default collapsed) and the existing debounce/resolve
machinery already waits for replayed state to settle.
- graph-layout.test.ts: +6 tests (exact ancestor keys for sequential / for_each
/ mixed cases, early stop past a materialized context, and integration checks
that the keys actually reveal the target node).
- docs/dashboard-deep-links.md: document reveal-inline behavior and correct the
stale "an agent inside a subworkflow won't be found by `?agent=` alone" claim
(transitive search now resolves it).
- Rebuild the static/ dashboard bundle.
Verified: tsc + Vitest 33/33 + vite build all green; live Playwright 5/5
(collapsed baseline; iteration + transitive deep-links reveal and center the
target with root nodes preserved; bogus subworkflow shows the "not found"
banner).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9af3cb49-70f2-42c7-b4ae-587b2820f924
Addresses code-review feedback on the expansion-aware deep-link change:
- centerOn timing (R1): the fixed 200ms setTimeout fired after the inline
reveal rebuilds the whole root graph, so a deeply-nested target could be
unmeasured when fitView ran. A node-targeted fitView on a not-yet-measured
node divides by a zero-size bounding box and yanks the viewport to the
origin at max zoom (target selected but off-screen, no error). Now poll
requestAnimationFrame until getInternalNode(id) is measured (width AND
height), then fit; fall back to a whole-graph fitView (with a warning) if it
never materializes. The rAF handle is tracked and cancelled on unmount.
- Reveal-or-error hardening (R2): wrap the resolution in try/catch so an
unexpected throw surfaces the error banner instead of a silent hung state.
- Breadcrumb (R3): warn if a reveal target path is not fully materialized
(currently unreachable — all callers pass validated paths).
- Tests: add two mixed sequential<->for_each nesting cases, exercising the
forEachGroupKey(parentPath, ...) non-root-parent branch that every prior
for_each test missed (verified the emitted keys and that they reveal the
target node).
- Docs: dashboard-deep-links.md now matches the actual resolver — removed the
never-implemented positional (agent#N) tier, corrected the bare-name
fallback (newest match wins, no ambiguity error), and fixed the edge-case
table and example URLs.
- Simplify expansionKeysForContextPath (drop a throwaway array copy by reading
the parent path before advancing the prefix) and revealContext (single store
snapshot).
- Rebuild the static/ bundle.
Verified: tsc clean, Vitest 35/35, vite build; live Playwright 4/4 (the target
now lands dead-center and on-screen across a fresh reload; error banner intact).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9af3cb49-70f2-42c7-b4ae-587b2820f924
@jrob5756
Jason Robert (jrob5756) marked this pull request as ready for review July 17, 2026 22:00
@jrob5756
Jason Robert (jrob5756) merged commit e58a2bd into mainJul 17, 2026
10 checks passed
@jrob5756
Jason Robert (jrob5756) deleted the feature/314-inline-subworkflow-expand-collapse branch July 17, 2026 22:01
Sign up for freeto 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.

Idea: inline expand/collapse for subworkflows in the dashboard graph (not drill-down only)

1 participant

@jrob5756