Skip to content

feat(apollo-react): add loop node [MST-8596]#556

Merged
SreedharAvvari merged 3 commits intomainfrom
feat/MST-8596-loop-container-node
Apr 29, 2026
Merged

feat(apollo-react): add loop node [MST-8596]#556
SreedharAvvari merged 3 commits intomainfrom
feat/MST-8596-loop-container-node

Conversation

@SreedharAvvari
Copy link
Copy Markdown
Contributor

@SreedharAvvari SreedharAvvari commented Apr 21, 2026

This PR adds a container-style loop node to apollo-react and wires the canvas preview/add-node flows needed to place real child nodes inside the loop body.

Core changes

  • adds LoopNode / LoopCanvasNode for manifests with display.shape: 'container', including the loop shell, header, body frame, empty-state add CTA, resize controls, adornments, status styling, and toolbar support
  • extends node manifests with the container shape and handle groups with boundary: 'outer' | 'inner', allowing loop handles to render on the outer shell or inner body frame while keeping React Flow edge anchors aligned
  • updates ButtonHandle, HandleButton, and NodeViewportOverlay so handle affordances can use separate visual/connection positions, inward-facing container handles, stable hit areas, and portal-rendered buttons/labels above React Flow node layers
  • introduces createPreviewGraph on top of createPreviewNode, supporting one or two preview edges, replacing an existing edge while inserting a node, and reparenting preview nodes into containers with parentId / extent: 'parent'
  • updates add-node flows (createAddNodePreview, connect-end insertion, edge-toolbar insertion, and AddNodeManager) to use preview graphs, preserve container parentage when materializing nodes, restore removed edges on cancellation, and treat any edge touching the preview node as a preview edge
  • maps container-shaped manifests to LoopCanvasNode in HierarchicalCanvas and Storybook helpers while keeping other manifest-driven nodes on BaseNode
  • updates container sizing constants, collapsed/expanded container sizing, React Flow z-index reset behavior, and toolbar/handle overlay layering needed for loop nodes
  • updates the Storybook uipath.control-flow.foreach manifest to shape: 'container' with outer input/success handles and inner Start, Continue, and Break handles

Reviewer focus

  • LoopNode.tsx / LoopCanvasNode.tsx: container rendering, resize behavior, empty-state first-child preview, inner/outer handle visibility, toolbar/adornment/status behavior
  • ButtonHandle.tsx / HandleButton.tsx / NodeViewportOverlay.tsx: separate visual vs connection anchors and portal layering for handle affordances and toolbars
  • createPreviewGraph.ts and callers: multi-edge preview creation, edge replacement/restoration, and reparenting previews into containers
  • HierarchicalCanvas.tsx and Storybook manifest helpers: container manifest detection and preview-edge cleanup during connect flows

Coverage

  • updates existing useAddNodeOnConnectEnd and useEdgeToolbarState unit tests for the move to showPreviewGraph
  • adds a Storybook Canvas/LoopNode scenario with connected ingress, loop body, and egress nodes

Ref: https://uipath.atlassian.net/browse/MST-8596

Copilot AI review requested due to automatic review settings April 21, 2026 04:50
@SreedharAvvari SreedharAvvari changed the title Feat/mst 8596 loop container node feat(apollo-react): add loop node [MST-8596] Apr 21, 2026
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from 32bb7bb to 2039ef0 Compare April 21, 2026 04:54
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new Loop container node experience to the canvas, including loop-scoped preview insertion behavior and improved handle/preview positioning semantics.

Changes:

  • Introduces LoopNode (component, types, helpers, preview utilities) plus Storybook fixtures and unit tests.
  • Extends preview-node creation to support centered placement and loop-aware edge insertion previews (including preview-edge detection).
  • Enhances handle behavior via connectionPosition, and preserves parented preview placement when materializing a real node.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/apollo-react/src/canvas/utils/createPreviewNode.ts Adds positionMode (drop vs center) and centered placement math for preview nodes.
packages/apollo-react/src/canvas/utils/createPreviewNode.test.ts Adds coverage for default drop semantics vs centered positioning.
packages/apollo-react/src/canvas/components/index.ts Re-exports LoopNode from the canvas components barrel.
packages/apollo-react/src/canvas/components/Toolbar/EdgeToolbar/useEdgeToolbarState.ts Attempts loop-aware preview creation first when inserting along an edge.
packages/apollo-react/src/canvas/components/Toolbar/EdgeToolbar/useEdgeToolbarState.test.ts Adds test ensuring loop preview utilities are preferred when applicable.
packages/apollo-react/src/canvas/components/LoopNode/index.ts Exports LoopNode and its types.
packages/apollo-react/src/canvas/components/LoopNode/LoopNode.types.ts Defines LoopNode public props/handles/header contract.
packages/apollo-react/src/canvas/components/LoopNode/LoopNode.tsx Implements resizable loop container UI, inner/outer handles, and drag accept/reject visuals.
packages/apollo-react/src/canvas/components/LoopNode/LoopNode.test.tsx Unit tests for header rendering, empty-state add, drag state, and resize affordances.
packages/apollo-react/src/canvas/components/LoopNode/LoopNode.stories.tsx Storybook fixtures for empty/connected loops, handle-add previews, and drag validation.
packages/apollo-react/src/canvas/components/LoopNode/LoopNode.preview.ts Adds loop-scoped preview graph creation and apply/remove helpers.
packages/apollo-react/src/canvas/components/LoopNode/LoopNode.preview.test.ts Tests centered previews, edge insertion previews, and apply/remove behavior.
packages/apollo-react/src/canvas/components/LoopNode/LoopNode.helpers.ts Helpers for wall handles, loop-body center, and dragged payload parsing.
packages/apollo-react/src/canvas/components/LoopNode/LoopNode.helpers.test.ts Tests helper behaviors (groups, center calc, payload parsing).
packages/apollo-react/src/canvas/components/Edges/SequenceEdge.tsx Treats edges connected to the preview node as preview edges (not only by preview edge id).
packages/apollo-react/src/canvas/components/ButtonHandle/ButtonHandle.tsx Adds connectionPosition so emitted direction/handle metadata can differ from visual placement.
packages/apollo-react/src/canvas/components/ButtonHandle/ButtonHandle.test.tsx Tests that connectionPosition drives emitted position while layout stays on position.
packages/apollo-react/src/canvas/components/ButtonHandle/ButtonHandle.styles.tsx Supports $visualPosition so layout can use visual placement while Handle uses connection placement.
packages/apollo-react/src/canvas/components/AddNodePanel/createAddNodePreview.ts Adds optional placement options (position + positionMode) for previews created from handles.
packages/apollo-react/src/canvas/components/AddNodePanel/AddNodeManager.tsx Preserves parentId/extent when turning a preview into a real node.
packages/apollo-react/src/canvas/components/AddNodePanel/AddNodeManager.test.tsx Adds test verifying parented preview placement is preserved on materialization.

Comment thread packages/apollo-react/src/canvas/components/LoopNode/LoopNode.preview.ts Outdated
Comment thread packages/apollo-react/src/canvas/components/LoopNode/LoopNode.preview.ts Outdated
Comment thread packages/apollo-react/src/canvas/utils/createPreviewNode.ts
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (PT)
apollo-design 🟢 Ready Preview, Logs Apr 28, 2026, 09:58:01 PM
apollo-docs 🟢 Ready Preview, Logs Apr 28, 2026, 09:56:12 PM
apollo-landing 🟢 Ready Preview, Logs Apr 28, 2026, 09:54:30 PM
apollo-ui-react 🟢 Ready Preview, Logs Apr 28, 2026, 09:56:27 PM
apollo-vertex 🟢 Ready Preview, Logs Apr 28, 2026, 09:56:06 PM

@github-actions github-actions Bot added pkg:apollo-react size:XXL 1,000+ changed lines. labels Apr 21, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

Dependency License Review

  • 2103 package(s) scanned
  • ✅ No license issues found
  • ⚠️ 15 package(s) excluded (see details below)
License distribution
License Packages
MIT 1824
ISC 104
Apache-2.0 69
BSD-3-Clause 30
BSD-2-Clause 24
Copyright 2022, UiPath, all rights reserved 9
BlueOak-1.0.0 8
MPL-2.0 5
MIT OR Apache-2.0 3
MIT-0 3
Unknown 3
Unlicense 3
CC0-1.0 3
LGPL-3.0-or-later 2
(MIT OR Apache-2.0) 2
Python-2.0 1
CC-BY-4.0 1
(MPL-2.0 OR Apache-2.0) 1
BSD 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.2.4 LGPL-3.0-or-later LGPL pre-built binary, not linked
@img/sharp-libvips-linuxmusl-x64 1.2.4 LGPL-3.0-or-later LGPL pre-built binary, not linked
@uipath/apollo-angular-elements 5.86.3 Copyright 2022, UiPath, all rights reserved UiPath first-party package
@uipath/apollo-core 4.35.0, 4.35.1 Copyright 2022, UiPath, all rights reserved UiPath first-party package
@uipath/apollo-fonts 1.25.8 Copyright 2022, UiPath, all rights reserved UiPath first-party package
@uipath/apollo-icons 1.33.7 Copyright 2022, UiPath, all rights reserved UiPath first-party package
@uipath/apollo-mui5 2.31.26 Copyright 2022, UiPath, all rights reserved UiPath first-party package
@uipath/portal-shell 3.351.4 Copyright 2022, UiPath, all rights reserved UiPath first-party package
@uipath/portal-shell-react 3.149.36 Copyright 2022, UiPath, all rights reserved UiPath first-party package
@uipath/portal-shell-types 3.325.2 Copyright 2022, UiPath, all rights reserved UiPath first-party package
@uipath/portal-shell-util 1.112.0 Copyright 2022, UiPath, all rights reserved UiPath first-party package
@uipath/apollo-lab 25.12.0 Unknown UiPath first-party package
@uipath/telemetry-client-web 5.1.0 Unknown UiPath first-party package
khroma 2.1.0 Unknown MIT per GitHub repo, missing license field in package.json
hyperx 2.5.4 BSD BSD-2-Clause per LICENSE file, non-SPDX "BSD" in package.json

Copilot AI review requested due to automatic review settings April 21, 2026 19:01
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from 2039ef0 to d48603d Compare April 21, 2026 19:01
@SreedharAvvari SreedharAvvari marked this pull request as ready for review April 21, 2026 19:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comment thread packages/apollo-react/src/canvas/utils/createPreviewGraph.ts
Comment thread packages/apollo-react/src/canvas/components/LoopNode/LoopNode.tsx
Comment thread packages/apollo-react/src/canvas/utils/createPreviewNode.ts Outdated
Comment thread packages/apollo-react/src/canvas/components/ButtonHandle/ButtonHandle.tsx Outdated
Copilot AI review requested due to automatic review settings April 22, 2026 17:37
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from 45a3534 to a84decf Compare April 22, 2026 17:37
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from a84decf to 3c9a626 Compare April 22, 2026 17:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated 3 comments.

Comment thread packages/apollo-react/src/canvas/components/ButtonHandle/ButtonHandle.tsx Outdated
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from 3c9a626 to 91d80d5 Compare April 22, 2026 17:50
Copilot AI review requested due to automatic review settings April 22, 2026 18:46
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from 91d80d5 to d1047d6 Compare April 22, 2026 18:46
@SreedharAvvari SreedharAvvari added the dev-packages Adds dev package publishing on pushes to this PR label Apr 22, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

📦 Dev Packages

🧹 Dev packages cleaned up after PR close.

Last updated: 2026-04-28 22:03:35 PT

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 4 comments.

Comment thread packages/apollo-react/src/canvas/components/LoopNode/LoopNodePreview.ts Outdated
Comment thread packages/apollo-react/src/canvas/utils/createPreviewNode.ts
Comment thread packages/apollo-react/src/canvas/components/ButtonHandle/ButtonHandle.tsx Outdated
Comment thread packages/apollo-react/src/canvas/schema/node-definition/handle.ts
Copy link
Copy Markdown
Contributor

@BenGSchulz BenGSchulz left a comment

Choose a reason for hiding this comment

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

I noticed some unexpected behavior when adding nodes within a loop.

The "add between" seems to preserve parenting (i.e. I can't move the node out of the container).

But the "add from handle" does not seem to preserve parenting. Is this expected, like we could technically break out of the loop that way? Or do we want to lock down parenting so any node created off of a node with a parent will also be parented?

Dragging a node within the container causes the full canvas to pan, which was a bit disorienting.

Screen.Recording.2026-04-22.at.1.32.58.PM.mov

@SreedharAvvari
Copy link
Copy Markdown
Contributor Author

I noticed some unexpected behavior when adding nodes within a loop.

The "add between" seems to preserve parenting (i.e. I can't move the node out of the container).

But the "add from handle" does not seem to preserve parenting. Is this expected, like we could technically break out of the loop that way? Or do we want to lock down parenting so any node created off of a node with a parent will also be parented?

Dragging a node within the container causes the full canvas to pan, which was a bit disorienting.

Screen.Recording.2026-04-22.at.1.32.58.PM.mov

The behaviour I was aiming for is -

  • any node added inside a loop container should be parented to that container by default
  • if the user drags that node outside the container, it should detach and move freely

So the reported issues are valid -

  • add from handle should preserve parenting when the new node belongs inside the container - will fix that
  • dragging a child out should detach it rather than causing the canvas to pan

My original thought was to keep the detachment logic in the consuming layer (flow-workbench), since that’s where the full reparenting/edge-repair behaviour belongs. But I agree the current Storybook demo feels broken/confusing without it, so for now I can add a small story-only helper to make the interaction behave as intended in the demo, while keeping the real production logic in flow-workbench. wdyt?

@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from d1047d6 to fe55e35 Compare April 23, 2026 13:44
Copilot AI review requested due to automatic review settings April 23, 2026 13:51
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from b1f8013 to b8727fd Compare April 27, 2026 08:46
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch 2 times, most recently from bf742a3 to 3bac354 Compare April 27, 2026 08:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 52 out of 52 changed files in this pull request and generated 1 comment.

Comment thread packages/apollo-react/src/canvas/utils/createPreviewGraph.ts
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch 2 times, most recently from b9d523d to 893886b Compare April 28, 2026 10:36
@SreedharAvvari SreedharAvvari requested a review from Copilot April 28, 2026 10:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 40 out of 40 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

packages/apollo-react/src/canvas/schema/node-definition/handle.ts:150

  • boundary was added to handleGroupManifestSchema, but instance-level handleGroupOverrideSchema still can’t specify a boundary. That means a node instance can’t override a handle group to be inner without failing schema validation, which makes the new container-handle feature incomplete for handle customizations. Consider adding boundary?: HandleBoundary to handleGroupOverrideSchema (and any downstream types) so overrides can target inner/outer boundaries too.
export const handleGroupManifestSchema = z.object({
  /** Position on the node */
  position: handlePositionSchema,

  /**
   * Optional boundary for container-style nodes.
   * `outer` renders on the node shell; `inner` renders on an inner frame/wall.
   * Defaults to `outer` when omitted.
   */
  boundary: handleBoundarySchema.optional(),

  customPositionAndOffsets: handleConfigurationSpecificPositionSchema.optional(),

  /** Array of handles at this position */
  handles: z.array(handleManifestSchema),

  /** Whether the handle group is visible */
  visible: z.boolean().optional(),
});

/**
 * Instance-level handle group replacement
 * Allows individual nodes to completely replace a handle group
 */
export const handleGroupOverrideSchema = z.object({
  /** Position identifier (matches manifest group) */
  position: handlePositionSchema,

  /** Replacement handles for this position */
  handles: z.array(handleManifestSchema),

  /** Whether the handle group is visible */
  visible: z.boolean().optional(),
});

@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from 893886b to 99645ab Compare April 28, 2026 11:05
Copilot AI review requested due to automatic review settings April 28, 2026 17:04
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 40 out of 40 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

packages/apollo-react/src/canvas/schema/node-definition/handle.ts:150

  • handleGroupManifestSchema now supports boundary: 'outer' | 'inner', but handleGroupOverrideSchema (used by node instance handleCustomization) can’t express or disambiguate boundary—especially now that a node can legitimately have multiple groups with the same position (outer + inner). This makes instance-level handle customization ambiguous for container nodes. Consider adding boundary to the override schema (and matching logic by {position, boundary}), or introducing a stable group identifier to target overrides reliably.
export const handleGroupManifestSchema = z.object({
  /** Position on the node */
  position: handlePositionSchema,

  /**
   * Optional boundary for container-style nodes.
   * `outer` renders on the node shell; `inner` renders on an inner frame/wall.
   * Defaults to `outer` when omitted.
   */
  boundary: handleBoundarySchema.optional(),

  customPositionAndOffsets: handleConfigurationSpecificPositionSchema.optional(),

  /** Array of handles at this position */
  handles: z.array(handleManifestSchema),

  /** Whether the handle group is visible */
  visible: z.boolean().optional(),
});

/**
 * Instance-level handle group replacement
 * Allows individual nodes to completely replace a handle group
 */
export const handleGroupOverrideSchema = z.object({
  /** Position identifier (matches manifest group) */
  position: handlePositionSchema,

  /** Replacement handles for this position */
  handles: z.array(handleManifestSchema),

  /** Whether the handle group is visible */
  visible: z.boolean().optional(),
});

Comment thread packages/apollo-react/src/canvas/utils/createPreviewGraph.ts
Comment thread packages/apollo-react/src/canvas/components/AddNodePanel/AddNodeManager.tsx Outdated
Comment thread packages/apollo-react/src/canvas/components/ButtonHandle/ButtonHandle.tsx Outdated
Comment thread packages/apollo-react/src/canvas/components/LoopNode/LoopNode.helpers.ts Outdated
Comment thread packages/apollo-react/src/canvas/utils/createPreviewGraph.ts
Copy link
Copy Markdown
Collaborator

@CalinaCristian CalinaCristian left a comment

Choose a reason for hiding this comment

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

Follow up (should log tickets if it makes sense to take them):

  • On empty loop, the + is not aligned with the start:
Image
  • Initial placement of the loop node should be centered by the edge (edge should go straight).
Image

@SreedharAvvari
Copy link
Copy Markdown
Contributor Author

Follow up (should log tickets if it makes sense to take them):

  • On empty loop, the + is not aligned with the start:
Image * Initial placement of the loop node should be centered by the edge (edge should go straight). Image * Adding a new node from the initally added node adds it outside (this is blocker for this PR in case it behaves the same way in flow workbench and it's not just a storybook isssue): [github.com/user-attachments/assets/74a883dd-4526-489a-b7b0-76c5702440d3](https://github.com/user-attachments/assets/74a883dd-4526-489a-b7b0-76c5702440d3) * Is the break handle optional ? (it should be toggle from the property panel in flow-workbench).

Thanks Cristian
For 1 - the prototype also has this issue, i agree with this & will follow up with design on the treatment for this

image

2 - I'm tracking this & fixed in #605
3 - looks like a bug, will fix this in the PR before merging
4 - yeah break will be optional & opt-in similar to error handle, will configure this in flow-workbench

Ticket for the issues 1, 2
https://uipath.atlassian.net/browse/MST-9193

Copilot AI review requested due to automatic review settings April 29, 2026 04:53
@SreedharAvvari SreedharAvvari force-pushed the feat/MST-8596-loop-container-node branch from 826aeb9 to 6dabf73 Compare April 29, 2026 04:53
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 43 out of 43 changed files in this pull request and generated 2 comments.

Comment on lines +32 to +110
export function getHandleActionPortal({
nodeId,
position,
positionPercent,
total,
nodeWidth,
nodeHeight,
}: HandleActionPortalOptions): HandleButtonPortal | undefined {
if (!nodeWidth || !nodeHeight) {
return undefined;
}

const edgeCoverageRatio = HANDLE_EDGE_COVERAGE_RATIO / total;
const horizontalWidth = nodeWidth * edgeCoverageRatio;
const verticalHeight = nodeHeight * edgeCoverageRatio;
const x = nodeWidth * (positionPercent / 100);
const y = nodeHeight * (positionPercent / 100);

switch (position) {
case Position.Top:
return {
nodeId,
left: x,
top: 0,
width: horizontalWidth,
height: HANDLE_CROSS_AXIS_SIZE_PX,
transform: 'translate(-50%, -50%)',
};
case Position.Bottom:
return {
nodeId,
left: x,
top: nodeHeight - HANDLE_CROSS_AXIS_SIZE_PX,
width: horizontalWidth,
height: HANDLE_CROSS_AXIS_SIZE_PX,
transform: 'translate(-50%, 50%)',
};
case Position.Left:
return {
nodeId,
left: 0,
top: y,
width: HANDLE_CROSS_AXIS_SIZE_PX,
height: verticalHeight,
transform: 'translate(-50%, -50%)',
};
case Position.Right:
return {
nodeId,
left: nodeWidth - HANDLE_CROSS_AXIS_SIZE_PX,
top: y,
width: HANDLE_CROSS_AXIS_SIZE_PX,
height: verticalHeight,
transform: 'translate(50%, -50%)',
};
}
}

export function getInwardHandleLayout(
position: Position,
handleType: HandleType
): InwardHandleLayout {
const notchOverlap = -INWARD_NOTCH_OVERLAP_PX[handleType];
const anchorSize = {
width: INWARD_HANDLE_ANCHOR_SIZE_PX,
height: INWARD_HANDLE_ANCHOR_SIZE_PX,
};

switch (position) {
case Position.Left:
return {
rootTransform: 'translate(0, -50%)',
contentDirectionClassName: 'flex-row',
notchStyle: { marginLeft: notchOverlap },
anchorStyle: {
...anchorSize,
left: `calc(100% - ${INWARD_HANDLE_ANCHOR_RADIUS_PX}px)`,
top: '50%',
transform: 'translateY(-50%)',
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

ButtonHandleLayoutUtils introduces new layout/positioning math that affects handle hit areas and portal anchoring, but it is currently untested. Since this logic is easy to regress and there are already tests for other ButtonHandle utilities, consider adding focused unit tests for getHandleActionPortal and getInwardHandleLayout (at least one per Position) to lock in expected geometry.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +232 to +237
const shouldShowHandles = (isConnecting || selected || isHovered) && !dragging;

const showHandleAddButtons = isDesignMode && !multipleNodesSelected && !isConnecting && !dragging;
const showResizeControls = selected && !dragging && isDesignMode;
const showEmptyStateButton = isDesignMode && !hasChildNodes && !!onAddFirstChild;

Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

This PR introduces a new LoopNode implementation with a fair amount of new interaction logic (empty-state CTA, hover/selection-driven handle visibility, resize snapping, toolbar portaling, container handle boundaries), but there are no unit/component tests alongside it. Given the existing test coverage for other canvas nodes/components, please add a LoopNode.test.tsx (and/or LoopCanvasNode.test.tsx) covering the core behaviors to prevent regressions.

Copilot generated this review using guidance from repository custom instructions.
@SreedharAvvari SreedharAvvari merged commit 7db2ef9 into main Apr 29, 2026
43 checks passed
@SreedharAvvari SreedharAvvari deleted the feat/MST-8596-loop-container-node branch April 29, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dev-packages Adds dev package publishing on pushes to this PR pkg:apollo-react size:XXL 1,000+ changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants