Skip to content

fix(node-editor): auto-layout on load + viewport to top-left#1465

Merged
gsxdsm merged 1 commit into
mainfrom
gsxdsm/node-editor-fix
Jun 6, 2026
Merged

fix(node-editor): auto-layout on load + viewport to top-left#1465
gsxdsm merged 1 commit into
mainfrom
gsxdsm/node-editor-fix

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

The workflow node editor was loading workflows with nodes at their persisted positions, which were often scattered or off-screen. Users had to manually click "Auto-layout" and pan the viewport to see the nodes every time they opened a workflow.

Solution

  1. Auto-layout on load: When a workflow is loaded, autoLayout() is now called automatically and the computed tidy positions are applied before the first render.
  2. Viewport positioned at top-left: After layout, the viewport is set to {x: 0, y: 0, zoom: 1} so the laid-out nodes are immediately visible in the top-left corner.
  3. Origin at (0,0): The auto-layout origin was changed from (40, 40) to (0, 0) so nodes start at the true top-left instead of being offset.

Changes

File Change
workflow-auto-layout.ts ORIGIN_X/ORIGIN_Y changed from 40 to 0
WorkflowNodeEditor.tsx Apply autoLayout + applyAutoLayout on load; call setViewport({x: 0, y: 0, zoom: 1})
WorkflowNodeEditor.test.tsx Updated test to verify auto-layout runs on load; added test for button still working

Verification

  • ✅ Lint passes
  • ✅ Build passes
  • ✅ Gate tests pass (1027 engine + 58 CI shape)
  • ✅ All 89 WorkflowNodeEditor tests pass

Summary by CodeRabbit

  • New Features
    • Workflows now automatically apply layout when loading, positioning nodes according to optimal spacing
    • Viewport resets to top-left corner on workflow load for consistent viewing experience
    • Auto-layout button remains available for manual layout adjustments

Run auto-layout automatically when a workflow is loaded so nodes are
tidily positioned instead of scattered at persisted positions. Position
the viewport at (0,0) with zoom 1 so the laid-out nodes are visible in
the top-left corner.

- workflow-auto-layout.ts: change ORIGIN_X/Y from 40 to 0
- WorkflowNodeEditor.tsx: apply autoLayout on load, setViewport to top-left
- WorkflowNodeEditor.test.tsx: update test to verify layout runs on load
@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Need the big picture first? Review this PR in Change Stack to see what changed before going file by file.

Review Change Stack

📝 Walkthrough

Walkthrough

WorkflowNodeEditor now automatically applies graph auto-layout during workflow load, repositioning nodes with zero-based origin coordinates. The editor captures React Flow's setViewport hook to position the canvas viewport at the top-left after layout. The dirty-tracking serialization is updated to match the post-layout node positions, and test coverage is adjusted to verify auto-layout runs on load.

Changes

Auto-layout on workflow load

Layer / File(s) Summary
Auto-layout origin and viewport hook setup
packages/dashboard/app/components/workflow-auto-layout.ts, packages/dashboard/app/components/WorkflowNodeEditor.tsx
Origin constants shift from (40, 40) to (0, 0) to reposition the layout baseline. The useReactFlow hook is imported and initialized to capture setViewport for programmatic viewport control.
Auto-layout application and viewport positioning in load effect
packages/dashboard/app/components/WorkflowNodeEditor.tsx
The workflow load effect computes auto-layout positions using the new origin, applies them to the converted nodes, updates the dirty-tracking snapshot to use laid-out nodes, and sets the viewport to { x: 0, y: 0, zoom: 1 } after loading.
Test updates for auto-layout on load behavior
packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx
The prior manual click test is replaced with an assertion that auto-layout runs automatically on load by checking the node's transform style becomes non-empty. A regression test confirms the auto-layout button remains available and clickable after initial auto-layout completes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A workflow now flows with grace so fine,
Auto-layout springs from origin line,
Nodes reposition with zero-based care,
Viewport springs to top-left with flair,
Tests confirm the canvas draws up-straight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(node-editor): auto-layout on load + viewport to top-left' accurately and specifically describes the main changes: enabling auto-layout on workflow load and positioning the viewport at the top-left corner.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gsxdsm/node-editor-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx (1)

430-457: ⚡ Quick win

Strengthen these tests to assert auto-layout behavior directly

The current checks can pass even if auto-layout-on-load/click behavior regresses. Prefer spying on autoLayout and asserting it is called on initial load and again when the button is clicked.

Suggested test hardening
+import * as autoLayoutModule from "../workflow-auto-layout";
...
 it("runs auto-layout on load (nodes are positioned at layout positions)", async () => {
   vi.mocked(fetchWorkflows).mockResolvedValue([v2Def()]);
-  const { container } = render(
-    <WorkflowNodeEditor isOpen onClose={() => {}} addToast={() => {}} />,
-  );
-  await screen.findByTestId("wf-node-start");
-  // React Flow positions step nodes via a translate transform on their wrapper.
-  const wrapperFor = (id: string) =>
-    container.querySelector<HTMLElement>(`.react-flow__node[data-id="${id}"]`);
-  // After load, the step node should have been auto-laid-out (positioned).
-  await waitFor(() => {
-    const transform = wrapperFor("step")?.style.transform ?? "";
-    expect(transform).not.toBe("");
-  });
+  const spy = vi.spyOn(autoLayoutModule, "autoLayout");
+  render(<WorkflowNodeEditor isOpen onClose={() => {}} addToast={() => {}} />);
+  await screen.findByTestId("wf-node-start");
+  await waitFor(() => expect(spy).toHaveBeenCalled());
+  spy.mockRestore();
 });
 
 it("clicking auto-layout still works after initial load", async () => {
   vi.mocked(fetchWorkflows).mockResolvedValue([v2Def()]);
+  const spy = vi.spyOn(autoLayoutModule, "autoLayout");
   render(
     <WorkflowNodeEditor isOpen onClose={() => {}} addToast={() => {}} />,
   );
   await screen.findByTestId("wf-node-start");
-  // The auto-layout button should still be present and clickable.
   const btn = screen.getByTestId("wf-auto-layout");
-  expect(btn).toBeInTheDocument();
-  // Clicking it should not throw.
+  const initialCalls = spy.mock.calls.length;
   fireEvent.click(btn);
+  await waitFor(() => expect(spy.mock.calls.length).toBeGreaterThan(initialCalls));
+  spy.mockRestore();
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx`
around lines 430 - 457, The tests should spy on the auto-layout function and
assert it runs on load and on button click: locate where WorkflowNodeEditor
instantiates or imports autoLayout (the autoLayout function used by the
editor/React Flow instance), mock/spy on that autoLayout function before
rendering in the two tests (the "runs auto-layout on load..." and "clicking
auto-layout still works..." tests), then assert the spy was called once after
initial render and again when the auto-layout button
(data-testid="wf-auto-layout") is clicked; keep existing fetchWorkflows mocking
(vi.mocked(fetchWorkflows).mockResolvedValue([v2Def()])) and use the same
render/screen utilities so only the addition of the spy and call assertions are
required.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx`:
- Around line 430-457: The tests should spy on the auto-layout function and
assert it runs on load and on button click: locate where WorkflowNodeEditor
instantiates or imports autoLayout (the autoLayout function used by the
editor/React Flow instance), mock/spy on that autoLayout function before
rendering in the two tests (the "runs auto-layout on load..." and "clicking
auto-layout still works..." tests), then assert the spy was called once after
initial render and again when the auto-layout button
(data-testid="wf-auto-layout") is clicked; keep existing fetchWorkflows mocking
(vi.mocked(fetchWorkflows).mockResolvedValue([v2Def()])) and use the same
render/screen utilities so only the addition of the spy and call assertions are
required.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b972bae1-0790-41be-9e71-7eece3cf03df

📥 Commits

Reviewing files that changed from the base of the PR and between e1d6e4e and d521661.

📒 Files selected for processing (3)
  • packages/dashboard/app/components/WorkflowNodeEditor.tsx
  • packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx
  • packages/dashboard/app/components/workflow-auto-layout.ts

@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR auto-applies layout on workflow load and resets the viewport to {x: 0, y: 0, zoom: 1} so nodes are visible immediately without manual steps. The loadedSnapshotRef is correctly rebased to the auto-layout positions, keeping the dirty-state logic coherent with the new on-load behaviour.

  • workflow-auto-layout.ts: ORIGIN_X/ORIGIN_Y dropped from 40 to 0, placing the first node flush against the canvas corner.
  • WorkflowNodeEditor.tsx: autoLayout + applyAutoLayout are called inside the load effect before setNodes, and setViewport is invoked at the end; setViewport is properly added to the dependency array.
  • WorkflowNodeEditor.test.tsx: the original test that verified a transform change after clicking the button is replaced by a weaker clickability assertion.

Confidence Score: 4/5

Safe to merge; the load effect and snapshot logic are coherent and the behaviour change is intentional.

The core load-effect changes are sound: auto-layout runs before setNodes, the snapshot is correctly rebased to laidOutNodes, and setViewport is properly scoped with the right dependency. The two remaining concerns are visual (zero-pixel margin at the canvas edge) and test coverage (the button repositioning behaviour is no longer asserted).

WorkflowNodeEditor.test.tsx — the button-click test lost its position-change assertion and should be strengthened.

Important Files Changed

Filename Overview
packages/dashboard/app/components/WorkflowNodeEditor.tsx Adds auto-layout on load and programmatic viewport reset; snapshot correctly rebased to laidOutNodes; setViewport dependency properly added to effect array.
packages/dashboard/app/components/workflow-auto-layout.ts ORIGIN_X/Y changed from 40 to 0, placing laid-out nodes flush against the top-left corner with no visual padding.
packages/dashboard/app/components/tests/WorkflowNodeEditor.test.tsx Replaces the transform-change assertion with a weaker clickability check; the new test does not verify that the button actually repositions nodes.

Sequence Diagram

sequenceDiagram
    participant User
    participant Editor as WorkflowNodeEditor
    participant RF as ReactFlow
    participant Layout as autoLayout()

    User->>Editor: Open workflow
    Editor->>Editor: irToFlow(activeWorkflow)
    Editor->>Layout: autoLayout(flow.nodes, flow.edges, columns)
    Layout-->>Editor: layoutPositions (Map)
    Editor->>Layout: applyAutoLayout(flow.nodes, layoutPositions)
    Layout-->>Editor: laidOutNodes
    Editor->>RF: setNodes(laidOutNodes)
    Editor->>RF: setEdges(flow.edges)
    Editor->>Editor: "loadedSnapshotRef = serializeGraph(..., laidOutNodes, ...)"
    Editor->>RF: "setViewport({x:0, y:0, zoom:1})"
    RF-->>User: Nodes visible at top-left

    User->>Editor: Click Auto-layout button
    Editor->>Layout: autoLayout(currentNodes, edges, columns)
    Layout-->>Editor: newPositions
    Editor->>RF: setNodes(applyAutoLayout(ns, newPositions))
    RF-->>User: Nodes repositioned
Loading

Reviews (1): Last reviewed commit: "fix(node-editor): auto-layout on load + ..." | Re-trigger Greptile

Comment on lines +446 to +457
it("clicking auto-layout still works after initial load", async () => {
vi.mocked(fetchWorkflows).mockResolvedValue([v2Def()]);
render(
<WorkflowNodeEditor isOpen onClose={() => {}} addToast={() => {}} />,
);
await screen.findByTestId("wf-node-start");
// The auto-layout button should still be present and clickable.
const btn = screen.getByTestId("wf-auto-layout");
expect(btn).toBeInTheDocument();
// Clicking it should not throw.
fireEvent.click(btn);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Auto-layout button test doesn't verify repositioning

The replacement test only asserts that the button exists and that clicking it doesn't throw — it doesn't verify that node positions actually change. The original test confirmed that style.transform changed after a click, giving real confidence that handleAutoLayout calls setNodes. If the click handler were silently disconnected or applyAutoLayout returned the same nodes, this test would still pass.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines 39 to +41
/** Left/top origin for the laid-out graph. */
const ORIGIN_X = 40;
const ORIGIN_Y = 40;
const ORIGIN_X = 0;
const ORIGIN_Y = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Zero-padding origin places nodes flush against the viewport edge

With ORIGIN_X/Y = 0 and setViewport({ x: 0, y: 0, zoom: 1 }), the first node's top-left corner renders at the exact top-left pixel of the canvas container, with no breathing room. The original 40 px offset was the visible margin between the canvas edge and the first card. Even a small value (e.g. 16 px) would prevent the node header from being clipped by the canvas border or the React Flow minimap chrome.

Suggested change
/** Left/top origin for the laid-out graph. */
const ORIGIN_X = 40;
const ORIGIN_Y = 40;
const ORIGIN_X = 0;
const ORIGIN_Y = 0;
/** Left/top origin for the laid-out graph. */
const ORIGIN_X = 16;
const ORIGIN_Y = 16;

@gsxdsm
gsxdsm merged commit 62f6036 into main Jun 6, 2026
5 checks passed
@gsxdsm
gsxdsm deleted the gsxdsm/node-editor-fix branch June 6, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant