Uh oh!
There was an error while loading. Please reload this page.
feat(web): inline expand/collapse for subworkflows in the dashboard graph - #316
Merged
Jason Robert (jrob5756) merged 5 commits intoJul 17, 2026
Merged
Conversation
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-587b2820f924Phase 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
Jason Robert (jrob5756)
marked this pull request as ready for review
July 17, 2026 22:00
Uh oh!
There was an error while loading. Please reload this page.
Jason Robert (jrob5756)
deleted the
feature/314-inline-subworkflow-expand-collapse
branch
July 17, 2026 22:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 stackedcommits, each independently verified (tsc + Vitest +
vite build+ a livePlaywright pass on a real workflow):
Phase 0 — foundation (
7412556)Context-namespaced node/edge IDs (
contextKey::name) so multiple contexts canrender at once without collisions; recursive nested-DAG layout (per-context
dagre → sized container →
parentId/extent: parent, boundary edges remappedto
ingress/egress);expandedContextsstore 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 isexpandable) +
Ekeyboard shortcut; bulkexpandContexts/collapseContextsstore actions;
collectExpandableContextKeys(bounded — does not descend intofor_eachiterations); not-started placeholder chevron; subtle expand animation.Phase 2 —
for_each-of-workflow inline sub-containers (d43ce55)A
for_eachgroup whose inline agent istype: workflowbecomes an expandablecontainer: 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 groupkeys 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 — expandits ancestor containers in the root graph and select+center it — instead of
re-rooting the view. New pure
expansionKeysForContextPath()walks only thetarget'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.mdupdated to the reveal-inline behavior.Verification
tsc -b,npm run test(Vitest 33/33),npm run build— all green.conductor validateon the two bundled examples(
examples/subworkflow-inline.yaml,examples/subworkflow-foreach-inline.yaml).--webrun (inlineexpand/collapse, Expand/Collapse-all +
E,for_eachiteration containers,and deep-link inline reveal incl. the transitive agent + "not found" cases).
Notes
static/dashboard bundle is rebuilt and committed (the serverserves
static/).