feat(apollo-react): add support for sequential canvas - #951
Conversation
Dependency License Review
License distribution
Excluded packages
|
There was a problem hiding this comment.
Pull request overview
Adds a new “sequential” canvas view to apollo-react/canvas, providing an n8n/Zapier-style vertical projection of the existing flow graph while keeping the canonical nodes/edges (and flow positions) as the source of truth. This introduces a projection/layout engine, new sequential UI components (rows, connectors, insert/move/collapse affordances), a flow↔sequential view switcher with persisted view + viewport restore, and supporting docs/i18n/test coverage.
Changes:
- Introduce sequential graph utilities (projection, layout, merge analysis, fingerprint memoization, slot/preview helpers, and mutations support).
- Add Sequential Canvas UI layer (SequentialCanvas, view switcher/context, sequential nodes/edges, insertion + move actions, collapse gutter, accessibility fallback list, story harness + stories).
- Extend shared canvas infrastructure to support sequential behavior (BaseNode “bar” variant, shared edge-label primitive, preview-handle resolution, and BaseCanvas opt-out of refit-on-node-set-change).
Reviewed changes
Copilot reviewed 104 out of 104 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/apollo-react/src/test/canvas-mocks.ts | Extend xyflow mock to include EdgeLabelRenderer for sequential edge label rendering in tests. |
| packages/apollo-react/src/canvas/utils/sequential/sequential.types.ts | Define sequential “frozen contract” types for projection/layout/connectors/slots/mutations. |
| packages/apollo-react/src/canvas/utils/sequential/previewRow.ts | Add pure helper to splice a preview row/connectors into a projection during Add Node preview. |
| packages/apollo-react/src/canvas/utils/sequential/previewRow.test.ts | Unit tests for preview-row splicing and geometry shift correctness. |
| packages/apollo-react/src/canvas/utils/sequential/mergeAnalysis.ts | Add structured merge-point approximation to support branch merge rendering/degradation. |
| packages/apollo-react/src/canvas/utils/sequential/layoutSequence.ts | Implement pure sequential layout pass and connector waypoint generation. |
| packages/apollo-react/src/canvas/utils/sequential/index.ts | Sequential utils barrel export. |
| packages/apollo-react/src/canvas/utils/sequential/graph-helpers.ts | Pure graph indexing/traversal helpers for projection/merge/layout. |
| packages/apollo-react/src/canvas/utils/sequential/fingerprint.ts | Structural fingerprint for projection/layout memoization. |
| packages/apollo-react/src/canvas/utils/sequential/fingerprint.test.ts | Tests for fingerprint invariants and invalidation triggers. |
| packages/apollo-react/src/canvas/utils/index.ts | Re-export sequential utils from canvas utils. |
| packages/apollo-react/src/canvas/storybook-utils/sequential/wireframeManifests.ts | Add manifests needed for sequential wireframe story node types. |
| packages/apollo-react/src/canvas/storybook-utils/sequential/SequentialCanvasStoryHarness.tsx | Story-only harness for controlled sequential graphs with registry manifests. |
| packages/apollo-react/src/canvas/storybook-utils/sequential/index.ts | Local barrel for sequential story helpers. |
| packages/apollo-react/src/canvas/README.md | Document sequential view model, usage, and degraded-graph behavior. |
| packages/apollo-react/src/canvas/locales/en.json | Add English strings for sequential canvas UI. |
| packages/apollo-react/src/canvas/locales/de.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/es.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/es-MX.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/fr.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/ja.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/ko.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/pt.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/pt-BR.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/ro.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/ru.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/tr.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/zh-CN.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/locales/zh-TW.json | Add sequential canvas message keys (currently English placeholders). |
| packages/apollo-react/src/canvas/hooks/usePreviewNode.ts | Improve preview-edge filtering and resolve existing-node handle ids for derived views. |
| packages/apollo-react/src/canvas/hooks/usePreviewNode.test.ts | Tests for preview-handle resolution behavior. |
| packages/apollo-react/src/canvas/constants.ts | Add sequential geometry constants and default target handle id. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/ViewSwitcher.tsx | Add segmented Flow/Sequential control with i18n + controlled state. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/ViewSwitcher.test.tsx | Test view switcher behavior and callback invocation. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/useSequentialMoveActionsValue.ts | Provide stable context value for move actions with latest-graph commit behavior. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/useSequentialMoveActionsValue.test.ts | Tests for identity stability and correct commit forwarding. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/useCanvasViewMode.ts | Persist view choice via useStorageState (localStorage). |
| packages/apollo-react/src/canvas/components/SequentialCanvas/ToggleHarness.tsx | Demo harness for view toggle + flow-position synthesis on return to flow. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/synthesizePositionsForFlow.ts | Place sequential-inserted nodes into non-overlapping flow positions on toggle. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/synthesizePositionsForFlow.test.ts | Tests for placement guarantees and marker cleanup. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialViewContext.tsx | Provide optional view + per-view viewport save/restore context. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialMoveActionsContext.tsx | Context for move-option calculation and commit wiring (optional outside canvas). |
| packages/apollo-react/src/canvas/components/SequentialCanvas/sequentialMoveActions.ts | Pure move-option computation, loop/bare-branch safety gates, slot resolution helpers. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialInsertStateContext.tsx | Provider to share pending insert slot state across hook instances. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialInsertGapContext.tsx | Provider for “active insert slot” reporting to drive preview-gap behavior. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialGutter.tsx | Step-number rail gutter with collapse/expand chevrons and leader lines. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/sequentialGraph.constants.ts | Shared sequential graph constants (synthetic ids/types, full-render threshold). |
| packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialCollapsedRowsContext.tsx | Context to pass collapsed-row ids to step nodes without mutating node data. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialCanvas.types.ts | Public SequentialCanvas props + ViewSwitcher props types. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialCanvas.roundtrip.test.ts | Acceptance test for insert → toggle-to-flow placement → re-project identity. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialAccessibleList.tsx | Screen-reader fallback list for very large graphs when visual virtualization is enabled. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/useSequentialMoveMenuItems.tsx | Build localized “move” kebab items (design-mode only) for sequential steps. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/useSequentialMoveMenuItems.test.tsx | Tests for menu item availability, disabled-state, and commit callbacks. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/SequentialStepNode.tsx | Wrapper rendering BaseNode in “bar” variant with collapse + move menu integration. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/SequentialStepNode.test.tsx | Lightweight wrapper test for bar variant rendering. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/SequentialStartNode.tsx | Synthetic start-row bar with optional “Add trigger” CTA and bottom handle. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/SequentialPlaceholderNode.tsx | Synthetic placeholder row/plus variants for empty lanes and append points. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/SequentialPlaceholderNode.test.tsx | Tests for placeholder variants and add behavior gated by mode. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/SequentialInsertPreviewNode.tsx | Sequential-specific Add Node preview bar rendering matching bar layout. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/index.ts | Local barrel and synthetic nodeTypes map for sequential view. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/nodes/BarVariant.stories.tsx | Story to compare card vs bar rendering drift and show synthetic rows. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/index.ts | Export sequential canvas components/hooks from SequentialCanvas package index. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/useSequentialInsert.ts | Hook wiring sequential insert affordance to existing preview + AddNodeManager pipeline. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/useSequentialInsert.test.tsx | Tests for slot publishing and shared pending-slot state across hook instances. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/SequentialInsertButton.tsx | Centered connector “plus” affordance rendered via EdgeLabelRenderer. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/sequentialConnectorStyle.ts | Map connector kind to stroke style (solid vs dashed). |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/sequentialConnectorStyle.test.ts | Tests for connector stroke-style mapping coverage. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/SequentialConnectorEdge.types.ts | Define SequentialConnectorData contract for reference-stable edge rendering. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/SequentialConnectorEdge.test.tsx | Tests for stroke style, insert affordance, left-entry arrow centering, and labels. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/SequentialBranchHeader.tsx | Render branch-entry header label using shared edge-label treatment. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/SequentialBranchHeader.test.tsx | Test branch header positioning + selected styling. |
| packages/apollo-react/src/canvas/components/SequentialCanvas/edges/index.ts | Local barrel for sequential edges/insert pipeline (kept out of public barrel until GA). |
| packages/apollo-react/src/canvas/components/index.ts | Export SequentialCanvas from the canvas components barrel. |
| packages/apollo-react/src/canvas/components/Edges/shared/primitives/index.ts | Export EdgeLabelContent in primitives barrel. |
| packages/apollo-react/src/canvas/components/Edges/shared/primitives/EdgeLabel.tsx | Factor shared label styling into EdgeLabelContent used by SVG + portal labels. |
| packages/apollo-react/src/canvas/components/Edges/shared/hooks/useEdgeGeometry.ts | Add configurable borderRadius for waypoint rounded-path generation. |
| packages/apollo-react/src/canvas/components/canvas-gallery.stories.tsx | Add Sequential Canvas to component gallery and align TaskIcon type usage. |
| packages/apollo-react/src/canvas/components/BaseNode/BaseNode.tsx | Add BaseNode “bar” render variant, sequential sizing/handles, and extra menu items support. |
| packages/apollo-react/src/canvas/components/BaseCanvas/BaseCanvas.types.ts | Add refitOnNodeSetChange option to control auto-layout refit behavior. |
| packages/apollo-react/src/canvas/components/BaseCanvas/BaseCanvas.tsx | Wire refitOnNodeSetChange into auto-layout hook. |
| packages/apollo-react/src/canvas/components/BaseCanvas/BaseCanvas.hooks.ts | Implement one-shot fit behavior when refit-on-node-set-change is disabled. |
| "sequential-canvas.start.add-trigger": "Add trigger", | ||
| "sequential-canvas.step.aria-label": "Step {stepNumber} of {total}: {label}", | ||
| "sequential-canvas.placeholder.add": "Add step", | ||
| "sequential-canvas.new-step": "New step", |
📊 Coverage + size by packagePer-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.
"Coverage" is each package's own |
| let stackLeftX = 0; | ||
| for (const position of positions.values()) { | ||
| if (position.x < stackLeftX) stackLeftX = position.x; | ||
| } |
| * TOPOLOGY mutation (D4), so -- like the ⊕ insert affordance | ||
| * (`SequentialConnectorEdge`'s `showInsert`) -- they only appear in design | ||
| * mode; outside a `SequentialMoveActionsContext` provider (isolated node | ||
| * stories/tests) this returns an empty array rather than throwing. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 114 out of 114 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialGutter.tsx:91
- The comment says the gutter reads the stack’s left edge from actual row positions (so it won’t assume
x = 0), butstackLeftXis initialized to0and only updates when a position is negative. If a future layout change shifts all rows to a positive x-offset, the gutter will still anchor at 0 and desync. Compute the true minimum x instead.
let stackLeftX = 0;
for (const position of positions.values()) {
if (position.x < stackLeftX) stackLeftX = position.x;
}
const columnLeftX = stackLeftX - SEQ_GUTTER_COLUMN_WIDTH_PX - SEQ_GUTTER_LEADER_GAP_PX;
| const onEdgesChange = useCallback( | ||
| (changes: Parameters<typeof applyEdgeChanges<Edge>>[0]) => | ||
| setEdges((current) => applyEdgeChanges(changes, current)), | ||
| [] | ||
| ); |
| /** | ||
| * Preserves an independent viewport for each presentation, even when the | ||
| * optional persisted SequentialViewProvider is absent. | ||
| */ |
51fd856 to
cf24732
Compare
|
Apollo Coded App preview deployments are ready.
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 117 out of 117 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
packages/apollo-react/src/canvas/components/SequentialCanvas/SequentialGutter.tsx:90
stackLeftXis initialized to 0 and only updated when a row is further left. If all row x positions are > 0 (or if a future layout introduces a positive left margin), the gutter will still assume the stack starts at x=0 and render the rail/leader too far left. Initialize from the actual min x (or from the first position) so the gutter truly derives the left edge frompositions.
// The stack's own left edge: `layoutSequence` always anchors depth-0 rows at
// x = 0 (utils/sequential/layoutSequence.ts, read-only reference), but this
// reads it from the actual positions rather than assuming it, so a future
// layout change (e.g. a reference chip rendered further left) can't silently
// desync the rail from the bars it labels.
let stackLeftX = 0;
for (const position of positions.values()) {
if (position.x < stackLeftX) stackLeftX = position.x;
}
e1d7463 to
ec68fe1
Compare
ec68fe1 to
a2099dc
Compare
a2099dc to
5f40770
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 119 out of 119 changed files in this pull request and generated no new comments.
Suppressed comments (2)
packages/apollo-react/src/canvas/components/Edges/shared/primitives/EdgeLabel.tsx:50
EdgeLabelContentdoes not include the truncation/overflow constraints thatEdgeLabelapplies (max-w-48 overflow-hidden text-ellipsis). For longer labels (e.g. branch lane names), this can expand the label indefinitely and overlap other UI instead of clipping like other edge labels.
export function EdgeLabelContent({ text, selected }: EdgeLabelContentProps) {
return (
<div
className={
selected
? `${EDGE_LABEL_VISUAL_CLASS} border-[var(--canvas-primary,var(--color-primary))]`
: `${EDGE_LABEL_VISUAL_CLASS} border-[var(--canvas-border,var(--color-border))]`
}
>
{text}
</div>
);
packages/apollo-react/src/canvas/utils/sequential/compatibility.test.ts:93
- This test uses a non-existent sticky-note node type (
'sticky-note'). In this codebase the actual sticky note node type is'stickyNote', so the test is currently exercising a fictional type and can mislead future readers about whatisSequenceNodeshould filter out.
Storybook visual diffBaseline is the deployed main Storybook, so changes merged to main after this branch was last updated can also appear here. Logs Updated (PT): Aug 11, 2026, 07:58:42 PM |
Introduce a sequential (list-style) canvas view for apollo-react as an alternative to the graph layout, with round-trip-safe view switching. - Projection and layout engine mapping the graph to ordered rows, with subtree-aware move operations and slot-based navigation - BaseNode bar render variant and sequential node shells aligned with card behavior for icons, selection, display, and a11y - Connector edges with smooth-step corners, shared branch spines, and dashed container merge-back routing - Insert pipeline with preview gap, shared cross-instance pending state, and container-aware parenting - Gutter, keyboard navigation, and aria labels - Virtualization past a node ceiling to avoid mount-burst loops, fitView on entry, and perf-loop fixes - Stories, i18n catalog, docs, and hardening tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
React Flow requires every parent to precede its children in the `nodes` array. When it does not, `updateChildNode` logs a warning and then leaves the child unparented for that pass: no `parentLookup` entry, and the child's relative `position` is used as an absolute one. That reads as broken containment even though `parentId` is correct the whole time. A nested for-each converted from the sequential view rendered its parent with an empty body and the "+ Add step" affordance, dragging the parent left the child behind, and the child sat at coordinates unrelated to the box it belonged to. Neither the projection nor the flow layout notices, because both bucket by `parentId` and are order-independent, so a mis-ordered graph projects and lays out perfectly and only misrenders. `prepareCanvasViewTransition` now repairs the order when entering flow view via `orderNodesParentsFirst`, a stable sort by containment depth that returns an already-ordered array by identity, keeps siblings in their relative order (array order is the layout's tie-breaker for same-rank nodes), and treats dangling `parentId`s and cycles as roots rather than dropping them. `applyGraphChangeSet` was why this class of bug was invisible to the suite: it appended re-added nodes, while production forwards a move as a single `replace` that React Flow applies in place. Outdenting a container under that helper genuinely produced child-before-parent order with nothing asserting otherwise, so the move tests were validating a shape production never emits. It now replaces in place. Also folds in a quality pass over the same area: - Register the `preview` node type for a host-supplied `flowNodeTypes` too. The injection sat after the early return, so the host most likely to render its own `AddNodeManager` through `children` was the one that never got it. Anything the host registered under `preview` still wins. - Point the container-predicate docs at the existing public `useCanvasNodeLayout()` instead of teaching hosts a fifth hand-written copy of `isContainerNodeManifest(registry.getManifest(node.type))`. - Drop three local xyflow mocks already provided globally by `test/canvas-mocks.ts`; each used `importOriginal()`, silently un-mocking every other xyflow export for those files. - Make the `CanvasView` list compiler-enforced via `Record<CanvasView, true>` and reuse the guard in `ViewSwitcher`, removing a third hand-written enumeration and its cast. - Assert `extent` in the insert parity test, which compared only `parentId` and so let the two paths drift. - Trim duplicated commentary and redundant test cases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
acb91a5 to
7444fbc
Compare
No description provided.