Skip to content

feat(apollo-react): sequential canvas view and sequenced runtime containers - #1405

Draft
snuziale wants to merge 4 commits into
mainfrom
feat/sequential-canvas-view
Draft

snuziale wants to merge 4 commits into
mainfrom
feat/sequential-canvas-view

Conversation

@snuziale

Copy link
Copy Markdown
Collaborator

Supersedes #951, which the stale bot closed.

Summary

Adds a sequential (n8n-style, vertical) presentation of the canvas to apollo-react, and lets a flow canvas show individual container bodies as sequences. The canonical nodes / edges stay the single source of truth; every sequential view is a pure projection, and canonical positions are never written from sequence geometry.

Three ways to use it:

  1. Whole-canvas toggle. SequentialCanvas with view="flow" | "sequential" flips the entire graph between cards and a numbered bar stack.
  2. Sequenced containers. On a flow canvas, any container node gets a flow/sequence switch in its header. In sequence mode its body renders as bars inside the frame, and only the container is draggable in the outer flow.
  3. Runtime containers. A container manifest with display.container.kind: 'runtime' and no inner-boundary handles is a self-contained subflow expanded inline: outer input and output only, no inner rail, no badge, and its sequence body opens with its own Workflow start row.

Commits

Each commit is a reviewable unit.

  1. feat: add sequential canvas view. The projection engine (utils/sequential), layout, compatibility report, mutations, and the SequentialCanvas component with insert, move, collapse, keyboard navigation, and the flow/sequential toggle.
  2. fix: keep parent nodes ahead of children in flow view. React Flow silently unparents a child that precedes its parent in the array.
  3. feat: bring sequential bars to parity with cards and harden the view. Review remediation, card-to-bar parity (all adornment corners, footer, placeholders, stacking, attachment chips for tools and resources), a performance pass (identity-stable contexts, one portal layer for insert buttons), shared helpers, and trimmed comments.
  4. feat: sequence bodies inside flow containers and add runtime containers. Per-container presentation, the header switch, in-frame numbering and collapse, nested steps, the runtime container, and the insert and drag fixes.

How to review

Storybook is the fastest way in:

Story What to look at
Components / Canvas / SequentialCanvas / Interactive Sequence Whole-canvas toggle, insert, move, collapse
Components / Canvas / SequentialCanvas / Card vs Bar Shape mapping, a 25-row state parity matrix, toggle round trip with tools as chips
Components / Canvas / SequentialCanvas / Sequence In Container The runtime container: numbers, collapse, nested For Each, insert inside the For Each, drag in sequence mode
Components / Nodes / ContainerNode / Body view switch, Runtime container The header switch states and the runtime manifest in flow mode

In code, start with utils/sequential/projectSequence.ts and projectContainerSequence.ts for the model, then components/SequentialCanvas/SequentialCanvas.tsx and useContainerSequences.ts for how it reaches React Flow. canvas/README.md has a "Sequential view" section with the public API, degraded-graph behavior, and the container limits.

Public API

From @uipath/apollo-react/canvas:

  • SequentialCanvas, SequentialCanvasProps (adds sequencedContainerIds, onSequencedContainerIdsChange, collapsedStepIds, readOnlyNodeIds; onAddTrigger now receives an optional container id)
  • ViewSwitcher, useCanvasViewMode, SequentialViewProvider, prepareCanvasViewTransition
  • The pure engine under canvas/utils: projectSequence, layoutSequence, analyzeSequentialCompatibility, projectContainerSequence, createSequencePredicates, and the mutation ops
  • isSelfContainedContainerManifest, RUNTIME_CONTAINER_KIND, isArtifactOnlyManifest in the container utils

The feature is pre-GA; the README says the props may still change.

Changes outside the sequential folder

Worth a closer look, since they touch shared code:

  • BaseNode: resolution logic moved into useBaseNodePresentation, shared with the new bar renderer. Card behavior is unchanged, verified line by line.
  • BaseCanvas: applies the parents-before-children repair to every host.
  • AddNodeManager: accepts an explicitly flagged unconnected preview (first step of a self-contained container). Every other preview behaves as before.
  • ContainerNode: renders the body view switch when a supporting canvas provides it, and hides resize handles and the empty state while sequenced.
  • usePreviewNode: handle resolution falls back to the manifest default for generic input / output ids, and its store selector exits early when no preview is selected.
  • Shared storybook fixtures: the first-run trigger gained an output handle and While became a container, which the sequential stories depend on. Expect related visual-diff changes.

Testing

Check Result
vitest run src/canvas 4247 passed, 1 failed: the pre-existing JsonTree.import timeout under full-suite load, which passes in isolation
pnpm build (including declaration files) passes
Biome clean on all changed files
Storybook each story above exercised by hand; drag in sequence mode sampled per animation frame with a constant frame size

Known limits

  • No move, delete, or keyboard navigation inside a sequenced container frame yet.
  • "Add step" in an empty sequenced body is not wired, since its slot has no source step.
  • No collapsed "subflow" presentation of a whole container.
  • A runtime container in flow body mode relies on the host's own AddNodeManager for its empty-state add, like other flow-view adds.
  • The loop's Sequential/Parallel pill still shows on For Each containers; only the runtime container drops it.

🤖 Generated with Claude Code

snuziale and others added 4 commits October 17, 2026 21:41
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>
…rden the view

Review remediation, parity, and performance pass over the sequential canvas.

Correctness
- removeStep heals only sequence edges; artifact edges are removed with the
  step, never re-sourced onto the predecessor.
- Keyboard shortcuts act only on canvas surfaces (pane, node, edge, the
  accessible list, the wrapper), so keys inside portaled menus and the Add
  Node panel no longer move selection or delete steps.
- Unplaceable sequence edges (second continuation, cross-scope, unreachable
  fan-in) render as goto connectors; compatibility reports cross-scope-edge
  and duplicate-edge instead of certifying the graph exact.
- In-body cycles walk from the first child and report cycle, not orphan.
- Lane order for undeclared branch nodes is deterministic across edits.
- BaseCanvas repairs parent-before-child ordering for every host.
- Bars read their declared height from the store; no measured-zero write-back.

Parity (toggle the view without losing anything)
- Bars render all four adornment corners, the footer, label placeholders,
  drillable and collapsed stacking, and hide the kebab while dragging.
- Artifact-only resources (tools, context, memory) are excluded from the
  projection and surface as attachment chips on their owning step; the
  compatibility report stays exact for an agent with tools.
- Icon tile radius follows the card shape (round for circle, pill for pill).
- readOnlyNodeIds is honored by delete and move.
- New "Card vs Bar" stories: shape mapping, state parity, toggle round trip.

Performance
- Context values read by every bar or edge are identity-stable across
  data-only edits (insert state, attachments, lane add, toolbar handler,
  read-only and collapsed sets, layout options).
- Insert buttons and branch headers render from one canvas-level portal
  layer instead of one portal per connector.
- Per-node handle resolution is cached; the preview-edge selector exits
  early when no preview node is selected; bars skip the redundant mount-time
  updateNodeInternals; the card keys its measure effect on a handle string.

Structure
- Shared helpers replace duplicated code: createSequencePredicates,
  indexNodesByParent, filterGraphByNodes, resolveSequenceMetrics,
  isArtifactOnlyManifest, buildEdgeId, shallowEqualExcept, and a map-aware
  getAbsolutePosition. moveStep is removed in favor of moveSubtree.
- prepareCanvasViewTransition accepts getManifest and derives the same
  predicates the canvas uses.
- Comments trimmed to the non-obvious why; the gallery link and README
  degraded-graphs section corrected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ntime containers

A flow canvas can now show any container's body as a sequence while the rest of
the canvas stays flow. The container's header carries a flow/sequence switch; in
sequence mode its steps render as bars inside the frame, only the container
itself is draggable in the outer flow, and the frame sizes to the stack.

Sequenced containers
- `sequencedContainerIds` / `onSequencedContainerIdsChange` on SequentialCanvas,
  host-owned with an internal fallback.
- `projectContainerSequence` projects one container's descendants as a scoped
  sequence (nested containers indent in place); slots keep the id of the
  container they belong to so inserts parent correctly.
- A card-or-bar dispatcher renders each node from view state, never node data.
- Change filtering drops projected geometry for bars and frame dimensions while
  passing selection, renames and the container's own moves.
- Insert buttons, the Add Node panel and the preview gap work inside frames,
  including slots inside a nested For Each; committed nodes get real flow
  positions and their containers grow, so the body reads correctly in flow.
- Step numbers and expand/collapse chevrons render in a gutter inside the frame.
- Clones stamp `measured` equal to their declared size, which stops the frame
  flickering between flow and sequence sizes while dragging.

Runtime containers
- `display.container.kind: 'runtime'` with no inner-boundary handles is a
  self-contained subflow expanded inline: outer input and output only, no inner
  rail, no header badge.
- Its sequence body opens with its own start row; Add trigger calls
  `onAddTrigger(containerId)`.
- In flow presentation an empty runtime container adds an unconnected first
  step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 01:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

Dependency License Review

  • ✅ 1947 package(s) scanned
  • ✅ No license issues found
  • ⚠️ 2 package(s) excluded (see details below)
License distribution
License Packages
MIT 1713
ISC 91
Apache-2.0 56
BSD-3-Clause 28
BSD-2-Clause 23
BlueOak-1.0.0 8
MPL-2.0 4
MIT-0 3
CC0-1.0 3
MIT OR Apache-2.0 2
(MIT OR Apache-2.0) 2
Unlicense 2
LGPL-3.0-or-later 1
Python-2.0 1
CC-BY-4.0 1
(MPL-2.0 OR Apache-2.0) 1
Unknown 1
Artistic-2.0 1
(WTFPL OR MIT) 1
(BSD-2-Clause OR MIT OR Apache-2.0) 1
CC-BY-3.0 1
0BSD 1
(MIT OR CC0-1.0) 1
MIT AND ISC 1
Excluded packages
Package Version License Reason
@img/sharp-libvips-linux-x64 1.3.4 LGPL-3.0-or-later LGPL pre-built binary, not linked
khroma 2.1.0 Unknown MIT per GitHub repo, missing license field in package.json

type TranslateFn = ReturnType<typeof useSafeLingui>['_'];

/** The connector draws its own dashes, so the preview style drops the Add Node dash pattern. */
const { strokeDasharray: _dash, ...SEQ_PREVIEW_EDGE_STYLE } = PREVIEW_EDGE_STYLE ?? {};
type TranslateFn = ReturnType<typeof useSafeLingui>['_'];

/** The connector draws its own dashes, so the preview style drops the Add Node dash pattern. */
const { strokeDasharray: _dash, ...SEQ_PREVIEW_EDGE_STYLE } = PREVIEW_EDGE_STYLE ?? {};
@github-actions

Copy link
Copy Markdown
Contributor

📊 Coverage + size by package

Per-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.

Package Coverage New-line coverage Packed (gzip) Unpacked vs main
@uipath/apollo-core — — 42.02 MB 50.17 MB ±0
@uipath/apollo-react 54.9% 87.0% (2392/2750) 8.15 MB 32.27 MB +163.7 KB
@uipath/apollo-ui-icons — — 2.86 MB 6.97 MB ±0
@uipath/apollo-vertex — — — — —
@uipath/apollo-wind — — 785.2 KB 4.55 MB +19 B
@uipath/ap-chat 85.8% — 40.37 MB 45.60 MB +1.3 KB

"Coverage" is each package's own coverage.include scope (e.g. apollo-core instruments only scripts/). "Packed"/"Unpacked" come from npm pack --dry-run and only cover built packages — "—" means not measured this run (package not affected / not built). "vs main" is the packed (gzipped) delta against the last successful main build (the package-sizes artifact from the Release workflow); "—" there means no main baseline was available this run. The baseline is main's latest build, not this PR's exact merge-base, so it includes any drift since the branch diverged. Packages with no vitest config are omitted.

@github-actions

Copy link
Copy Markdown
Contributor

Storybook visual diff

⚠️ Visual changes detected: 14 changed, 18 added (of 420 compared, 388 unchanged). View report

Baseline is the deployed main Storybook, so changes merged to main after this branch was last updated can also appear here. Logs

Updated (PT): Oct 18, 2026, 06:28:04 PM

This branch was successfully deployed

4 active deployments
preview/apollo-design — 0a021998 Deployed Oct 19, 2026 by github-actions[bot]
preview/apollo-vertex — 0a021998 Deployed Oct 19, 2026 by github-actions[bot]
preview/apollo-landing — 0a021998 Deployed Oct 19, 2026 by github-actions[bot]
preview/apollo-docs — 0a021998 Deployed Oct 19, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg:apollo-react size:XXL 1,000+ changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants