feat(studio): inline push-down expansion of loop/parallel/try_catch regions on the flow canvas (#2670 Phase 2)#2680
Merged
Conversation
…Geometry) #2670 Phase 2 groundwork. Cards are about to become variable-height (an expanded loop/parallel/try_catch container grows to embed its regions), so the layered layout's fixed `l * (NODE_H + V_GAP)` pitch becomes CUMULATIVE: each layer starts below the tallest auto-laid card of the previous occupied layer (empty layer indices keep the historical pitch so degenerate cyclic graphs are unchanged too). - New `computeLayoutWithGeometry(nodes, edges, heightOf?)` returning { positions, heights, size }; `computeLayout` is now a thin constant-height wrapper — its output is IDENTICAL to before, locked by invariance tests over five graph shapes (linear / diamond / back-edge / cycle island / manual ui). - Manual-`ui` nodes are excluded from a row's height (they don't render in their computed slot) — a pinned node at/below an expanded container can overlap it; accepted + documented. - `bottomAnchor(p, height = NODE_H)` so an expanded container's outgoing edge can leave from its true bottom; default keeps every existing call site and the symbolic anchor tests untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VuvxWgoadqryqBcjs7TpVi
…renderer #2670 Phase 2. An expanded container's height must be known BEFORE render (the layout pushes lower layers down with no measure-then-reflow pass), so predictor and renderer must agree to the pixel: - New pure `flow-region-metrics.ts` — the shared box-model constants (block border/padding, pinned 16px label row, region gap, tray chrome, EXPANDED_REGION_MAX_W) plus `predictRegionsHeight` / `predictExpandedNodeHeight`, mirroring the renderer with the same float expressions (no rounding skew). Imports only from flow-canvas-layout — dependency stays one-directional. - `flow-region-view.tsx` — every height-stack dimension becomes an explicit px style sourced from the metrics module (rem-based `gap-2`/`p-1.5` and the font-metric label line-height would drift): visual no-op at the 16px root font, but now exactly predictable. - Unit tests pin the formula (empty → NODE_H; single unlabeled body ≈187.4px; per-branch label row + gap; never-upscale; wide-region downscale). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VuvxWgoadqryqBcjs7TpVi
…(replaces Phase-1 popover) #2670 Phase 2. A loop/parallel/try_catch container card now expands IN PLACE: the chevron grows the card to embed its region(s) as a read-only mini-canvas (FlowRegionView at EXPANDED_REGION_MAX_W), and the geometry-aware layout pushes the layers below down by the card's true height. Collapsed by default; expansion is session-only FlowCanvas state (never written to the draft). - NodeCard: controlled `expanded`/`onToggleExpand` + `height` (the SAME number the layout used to position everything below — DOM and layout cannot disagree); header band pinned at NODE_H; `data-node-id` hook; the Phase-1 popover block is removed (chevron now toggles inline; Popover imports stay — NodePalette uses them). - FlowCanvas: `expandedIds` view state, memoized `regionsByNode` (also fixes per-render extractRegions identity churn), `nodeHeights` via predictExpandedNodeHeight, layout switched to computeLayoutWithGeometry; expanded source cards' outgoing edges leave from their true bottom (back-edges stay header-anchored so toggling never swings arcs); reveal centering uses real heights. - Tests: inline expand/collapse containment (not portaled), parallel branch labels, legacy flat loop unchanged, and a push-down assertion — the layer below moves by exactly predictExpandedNodeHeight − NODE_H and the card renders at exactly the predicted height. - Changeset rewritten for the shipped behavior (Phase-1 popover was never released); documents the manual-position overlap limitation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VuvxWgoadqryqBcjs7TpVi
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 19, 2026 12:42
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 free
to 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.
Summary
Phase 2 of #2670. A
loop/parallel/try_catchcontainer card now expands in place: the chevron grows the card to embed its region(s) as a read-only mini-canvas, and the canvas layout pushes the layers below down by the card's true height — real spatial nesting (the Power-Automate/BPMN-subprocess model), replacing the Phase-1 floating popover (#2675, merged but never released — the changeset is rewritten in place, so only the final behavior ever ships).Chosen over keeping the popover per the product direction (long-term platform correctness + best UX): inline nesting is the durable canvas model and the substrate the upcoming nested selection/inspector increment needs; a transient popover can't host persistent selection state.
What changed (3 commits, each independently green)
computeLayoutWithGeometry(flow-canvas-layout.ts) — the layered layout's fixedl × (NODE_H + V_GAP)pitch becomes cumulative: each layer starts below the tallest auto-laid card of the previous occupied layer (empty layer indices keep the historical pitch). Returns{ positions, heights, size };computeLayoutis now a thin constant-height wrapper — byte-identical output, locked by invariance tests over five graph shapes (linear / diamond / back-edge / cycle island / manual-ui).bottomAnchor(p, height = NODE_H)lets an expanded card's outgoing edge leave from its true bottom; default keeps all existing call sites + symbolic anchor tests untouched.flow-region-metrics.ts(new, pure) — the deterministic box model shared by the height predictor and the region renderer (no ResizeObserver, no measure-then-reflow):predictRegionsHeight/predictExpandedNodeHeightmirror the renderer with the same float expressions.flow-region-view.tsx's height stack becomes explicit px sourced from this module (rem/font-metric drift eliminated; visual no-op at the 16px root).NodeCardgains controlledexpanded/onToggleExpand/height(the same number the layout used to position everything below, so DOM and layout cannot disagree) and renders the region tray below its NODE_H header band;FlowCanvasownsexpandedIds(session-only view state, never written to the draft), memoizedregionsByNode,nodeHeights, edge-layer true-bottom anchors (back-edges stay header-anchored so toggling never swings arcs), and height-aware reveal centering.Collapsed by default → flows with no expanded container render exactly as before. Legacy flat loops (no
config.body) and ordinary nodes are untouched.Verification
flow-canvas-layout.test.ts(constant-height invariance ×5 graphs, cumulative offsets, tall-bottom sizing, manual-node exclusion),flow-region-metrics.test.ts(formula pinned, never-upscale),FlowCanvas.test.tsx(inline expand/collapse containment, parallel labels, legacy loop, and a push-down assertion: the layer below moves by exactlypredictExpandedNodeHeight − NODE_Hand the card renders at exactly the predicted height).turbo type-check29/29 · ESLint 0 errors.Notes / accepted limitations
node.ui) sitting at/below an expanded container can overlap it (manual positions are absolute; pinned nodes are deliberately excluded from row heights to avoid phantom gaps). Documented in JSDoc + changeset; drag it clear or collapse.max-h-[60vh]scroll valve is gone — a pathological many-branch container makes a very tall card; the canvas pans/zooms andsizegrows correctly.Next increments on #2670 (not in this PR): nested selection + structured region inspector, nested editing, recursive expansion, side-by-side parallel branches, client-side region validation. This PR does not close #2670.
🤖 Generated with Claude Code
Generated by Claude Code