Skip to content

fix: Canvas auto-save is never enabled on a fresh browser when versioning is on#3756

Merged
forestileao merged 4 commits intomainfrom
cursor/agent-13155843
Mar 27, 2026
Merged

fix: Canvas auto-save is never enabled on a fresh browser when versioning is on#3756
forestileao merged 4 commits intomainfrom
cursor/agent-13155843

Conversation

@cursor
Copy link
Copy Markdown
Contributor

@cursor cursor bot commented Mar 27, 2026

Summary

Fixes #3750

When canvas versioning is enabled, opening the UI in a fresh browser (no localStorage) caused the auto-save indicator to always show "Unsaved". This happened because isAutoSaveEnabled was still being read from localStorage, and the default for a fresh browser was effectively false when the stored value was absent and parsed incorrectly.

Root Cause

The auto-save toggle was previously removed from the UI, but the underlying localStorage-based state management (CANVAS_AUTO_SAVE_STORAGE_KEY) was never replaced with a static true. The canAutoSave flag still depended on isAutoSaveEnabled (from localStorage) when versioning was enabled, so a fresh browser with no stored value would not auto-save.

Changes

web_src/src/pages/workflowv2/index.tsx

  • Removed CANVAS_AUTO_SAVE_STORAGE_KEY constant and all localStorage read/write for auto-save
  • Simplified canAutoSave to !isTemplate && hasEditableVersion (always enabled when conditions are met)
  • Removed handleToggleAutoSave callback
  • Removed isAutoSaveEnabled, onToggleAutoSave, autoSaveDisabled, autoSaveDisabledTooltip props from CanvasPage rendering

web_src/src/ui/CanvasPage/index.tsx

  • Removed isAutoSaveEnabled, onToggleAutoSave, autoSaveDisabled, autoSaveDisabledTooltip from all interface definitions and prop threading (3 interfaces, 4 render sites)

web_src/src/ui/CanvasPage/Header.tsx

  • Removed auto-save toggle props from HeaderProps interface
  • Removed the auto-save Switch toggle UI from the header
  • Removed unused Switch import

test/e2e/canvas_auto_save_test.go (new)

  • E2E test that verifies auto-save works on a versioned canvas:
    1. Creates a canvas with organization versioning enabled
    2. Enters edit mode
    3. Adds a noop node and waits for save
    4. Dismisses the sidebar, then moves the node by dragging its header
    5. Asserts the save status indicator reaches "saved" state

Result

Auto-save is now statically enabled whenever the canvas has an editable version and is not a template. The header correctly reflects the saved/synced state instead of perpetually showing "Unsaved".

Open in Web Open in Cursor 

…pendency

When canvas versioning is enabled, auto-save is now always active
regardless of localStorage state. This fixes a regression where a fresh
browser (no localStorage) would default auto-save to disabled, causing
the header to perpetually show 'Unsaved'.

Changes:
- Remove CANVAS_AUTO_SAVE_STORAGE_KEY and localStorage read/write
- Simplify canAutoSave to depend only on isTemplate and hasEditableVersion
- Remove handleToggleAutoSave callback
- Remove auto-save toggle Switch UI from Header
- Remove isAutoSaveEnabled/onToggleAutoSave/autoSaveDisabled props from
  CanvasPage and Header prop chains

Closes #3750
@superplanehq-integration
Copy link
Copy Markdown

👋 Commands for maintainers:

  • /sp start - Start an ephemeral machine (takes ~30s)
  • /sp stop - Stop a running machine (auto-executed on pr close)

@forestileao
Copy link
Copy Markdown
Collaborator

/sp start

@superplanehq-integration
Copy link
Copy Markdown

✅ Ready.

Web: https://pr-3756-ephemeral.superplane.com
SSH: ssh -o StrictHostKeyChecking=no app@178.104.15.210
Logs: ssh -o StrictHostKeyChecking=no app@178.104.15.210 'cd superplane && make dev.logs.app'

@forestileao
Copy link
Copy Markdown
Collaborator

@cursoragent Create an e2e test for that:

  • create a canvas with versioning enabled
  • click on edit to join edit mode
  • move any node there
  • check if the label says saved in header after some seconds

@cursor
Copy link
Copy Markdown
Contributor Author

cursor bot commented Mar 27, 2026

Added the requested e2e test in test/e2e/canvas_auto_save_test.go. The test follows the existing patterns from canvas_change_requests_test.go and:

  • Creates a canvas with organization versioning enabled
  • Clicks "Edit" to enter edit mode
  • Adds a noop node and waits for the save status to reach "saved"
  • Moves the node by dragging its header (mouse down, move with offset, mouse up)
  • Waits for the save status indicator to show "saved" again, confirming auto-save works

The test compiles cleanly with go vet. The PR has been updated with the new test.

Open in Web Open in Cursor 

Verifies that a versioned canvas auto-saves after moving a node:
- Creates a canvas with organization versioning enabled
- Enters edit mode
- Adds a noop node and waits for save
- Moves the node by dragging it
- Asserts the save status indicator reaches 'saved' state
@forestileao
Copy link
Copy Markdown
Collaborator

@cursoragent didnt work
test_session.go:208: Clicking button "header button:has-text("Edit")"
2026/03/27 14:41:32 TACKLE: Connecting...
time="Mar 27 14:41:32" level=debug msg="Broadcasted canvas_version_updated event to workflow cff2d49a-9801-44d0-9103-ff4f7c30f26c"
time="Mar 27 14:41:32" level=info msg="handled request" duration=39.610539ms method=POST path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
test_session.go:286: Asserting visibility of "header button:has-text("Propose Change")"
time="Mar 27 14:41:33" level=info msg="handled request" duration=8.228486ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=8.623651ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/change-requests status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=11.776944ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=8.953015ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/memory status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=12.358365ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=2.057252ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=2.188095ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=12.89405ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=5.657241ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=11.241802ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:33" level=info msg="handled request" duration=3.164567ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions/dc9fc3a4-dd7c-4a5c-a14b-6b1bdd4da927 status=200
time="Mar 27 14:41:33" level=error msg="failed to list agent chats for org 9aa5005b-4420-4968-aea3-363ff95fbeaa, user a5b719ef-c3ec-4842-9815-9d4480143357, canvas cff2d49a-9801-44d0-9103-ff4f7c30f26c" error="rpc error: code = Unknown desc = Exception calling application: connection failed: connection to server at "172.18.0.3", port 5432 failed: FATAL: database "agents_dev" does not exist"
time="Mar 27 14:41:33" level=info msg="handled request" duration=18.172356ms method=GET path=/api/v1/agents/chats status=503
time="Mar 27 14:41:34" level=error msg="failed to list agent chats for org 9aa5005b-4420-4968-aea3-363ff95fbeaa, user a5b719ef-c3ec-4842-9815-9d4480143357, canvas cff2d49a-9801-44d0-9103-ff4f7c30f26c" error="rpc error: code = Unknown desc = Exception calling application: connection failed: connection to server at "172.18.0.3", port 5432 failed: FATAL: database "agents_dev" does not exist"
time="Mar 27 14:41:34" level=info msg="handled request" duration=20.58078ms method=GET path=/api/v1/agents/chats status=503
test_context.go:231: [Browser Logs] 503 http://127.0.0.1:8001/api/v1/agents/chats?canvasId=cff2d49a-9801-44d0-9103-ff4f7c30f26c
test_context.go:211: [console.error] Failed to load resource: the server responded with a status of 503 (Service Unavailable)
test_context.go:231: [Browser Logs] 503 http://127.0.0.1:8001/api/v1/agents/chats?canvasId=cff2d49a-9801-44d0-9103-ff4f7c30f26c
test_context.go:211: [console.error] Failed to load resource: the server responded with a status of 503 (Service Unavailable)
test_context.go:211: [console.warning] Failed to load chat sessions: {code: 14, message: failed to list agent chats, details: Array(0)}
test_session.go:243: Dragging element "testID="building-block-noop"" to "testID="rf__wrapper"" with offset (500, 220)
2026/03/27 14:41:36 TACKLE: Connecting...
test_session.go:115: Sleeping for 500 ms
time="Mar 27 14:41:36" level=debug msg="Broadcasted canvas_version_updated event to workflow cff2d49a-9801-44d0-9103-ff4f7c30f26c"
time="Mar 27 14:41:36" level=info msg="handled request" duration=10.821233ms method=PUT path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions/dc9fc3a4-dd7c-4a5c-a14b-6b1bdd4da927 status=200
test_session.go:117: Woke up after 500 ms
test_session.go:229: Filling in "testID="node-name-input"" with "Auto Save Node"
time="Mar 27 14:41:38" level=info msg="handled request" duration=6.976506ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:38" level=info msg="handled request" duration=6.615823ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:39" level=info msg="handled request" duration=2.956847ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:39" level=info msg="handled request" duration=4.550042ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/change-requests status=200
time="Mar 27 14:41:39" level=info msg="handled request" duration=9.03975ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/change-requests status=200
2026/03/27 14:41:41 TACKLE: Connecting...
time="Mar 27 14:41:41" level=debug msg="Broadcasted canvas_version_updated event to workflow cff2d49a-9801-44d0-9103-ff4f7c30f26c"
time="Mar 27 14:41:41" level=info msg="handled request" duration=22.046225ms method=PUT path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions/dc9fc3a4-dd7c-4a5c-a14b-6b1bdd4da927 status=200
time="Mar 27 14:41:41" level=info msg="handled request" duration=1.510957ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:41" level=info msg="handled request" duration=3.568746ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:41" level=info msg="handled request" duration=2.015656ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/versions status=200
time="Mar 27 14:41:41" level=info msg="handled request" duration=16.262904ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/change-requests status=200
time="Mar 27 14:41:41" level=info msg="handled request" duration=14.767606ms method=GET path=/api/v1/canvases/cff2d49a-9801-44d0-9103-ff4f7c30f26c/change-requests status=200
test_session.go:115: Sleeping for 300 ms
test_session.go:117: Woke up after 300 ms
canvas_auto_save_test.go:75:
Error Trace: /app/test/e2e/canvas_auto_save_test.go:75
/app/test/e2e/canvas_auto_save_test.go:24
Error: Received unexpected error:
playwright: timeout: Timeout 10000ms exceeded.
Test: TestCanvasAutoSave/versioned_canvas_auto-saves_after_moving_a_node
test_session.go:103: Taking screenshot: /app/tmp/screenshots/TestCanvasAutoSave/versioned_canvas_auto-saves_after_moving_a_node-1774622514208.png

@cursor
Copy link
Copy Markdown
Contributor Author

cursor bot commented Mar 27, 2026

Fixed two issues in the e2e test:

  • Node header selector: q.TestID("node", "Auto Save Node", "header") was producing node-auto save node-header (spaces preserved). The DOM uses node-auto-save-node-header (spaces converted to dashes). Switched to a q.Locator with proper strings.ReplaceAll(safe, " ", "-") conversion, matching the pattern used in canvas_page_test.go.
  • Sidebar blocking drag: After adding the noop node, the settings sidebar stays open and can overlap the node. Added a dismissSidebar() step (using ClickOnEmptyCanvasArea) before attempting to move the node.
Open in Web Open in Cursor 

- Use q.Locator with proper space-to-dash conversion for node header
  data-testid (q.TestID does not convert spaces within segments)
- Dismiss sidebar before moving node to ensure the node is accessible
- Add helper nodeHeaderSelector matching the DOM convention
@forestileao forestileao merged commit dd0d2a5 into main Mar 27, 2026
5 checks passed
@forestileao forestileao deleted the cursor/agent-13155843 branch March 27, 2026 15:02
EtnDiaz pushed a commit to EtnDiaz/superplane that referenced this pull request Mar 28, 2026
…ning is on (superplanehq#3756)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

Fixes superplanehq#3750

When canvas versioning is enabled, opening the UI in a fresh browser (no
localStorage) caused the auto-save indicator to always show "Unsaved".
This happened because `isAutoSaveEnabled` was still being read from
localStorage, and the default for a fresh browser was effectively
`false` when the stored value was absent and parsed incorrectly.

## Root Cause

The auto-save toggle was previously removed from the UI, but the
underlying localStorage-based state management
(`CANVAS_AUTO_SAVE_STORAGE_KEY`) was never replaced with a static
`true`. The `canAutoSave` flag still depended on `isAutoSaveEnabled`
(from localStorage) when versioning was enabled, so a fresh browser with
no stored value would not auto-save.

## Changes

**`web_src/src/pages/workflowv2/index.tsx`**
- Removed `CANVAS_AUTO_SAVE_STORAGE_KEY` constant and all localStorage
read/write for auto-save
- Simplified `canAutoSave` to `!isTemplate && hasEditableVersion`
(always enabled when conditions are met)
- Removed `handleToggleAutoSave` callback
- Removed `isAutoSaveEnabled`, `onToggleAutoSave`, `autoSaveDisabled`,
`autoSaveDisabledTooltip` props from `CanvasPage` rendering

**`web_src/src/ui/CanvasPage/index.tsx`**
- Removed `isAutoSaveEnabled`, `onToggleAutoSave`, `autoSaveDisabled`,
`autoSaveDisabledTooltip` from all interface definitions and prop
threading (3 interfaces, 4 render sites)

**`web_src/src/ui/CanvasPage/Header.tsx`**
- Removed auto-save toggle props from `HeaderProps` interface
- Removed the auto-save `Switch` toggle UI from the header
- Removed unused `Switch` import

**`test/e2e/canvas_auto_save_test.go`** (new)
- E2E test that verifies auto-save works on a versioned canvas:
  1. Creates a canvas with organization versioning enabled
  2. Enters edit mode
  3. Adds a noop node and waits for save
  4. Dismisses the sidebar, then moves the node by dragging its header
  5. Asserts the save status indicator reaches "saved" state

## Result

Auto-save is now statically enabled whenever the canvas has an editable
version and is not a template. The header correctly reflects the
saved/synced state instead of perpetually showing "Unsaved".
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-033b248e-abec-4a5b-8d51-1aab0c2fbddd"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-033b248e-abec-4a5b-8d51-1aab0c2fbddd"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Pedro Leão <60622592+forestileao@users.noreply.github.com>
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.

Canvas auto-save is never enabled on a fresh browser when versioning is on

2 participants