Skip to content

fix(canvas): a new document starts unlocked, and the edit lock blocks every user edit (v0.21.3) #334

Description

@MerciHanrim

The bug

After File → New, the new, empty document is still edit-locked when the previous document was locked: the lock control stays pressed, the canvas cannot be dragged or deleted from, and the empty-canvas hint still says "Drag a piece from the top bar onto the canvas". A reload keeps the lock. Measured on production v0.21.2 (c5e7d8f) from all four ways a document becomes locked: the Early MMO template (it opens locked by design), a template locked by hand, an imported file with recommendedRunConfig.canvasLocked: true, and a share link made from a locked document opened in a second browser. An unlocked document is unaffected.

While locked, some changes still go through: a palette click adds a node (which can then be neither moved nor deleted), Insert module adds its nodes (97 to 107 in the MMO template), and the toolbar Undo button restores the previous document.

Cause

  • graphStore.newGraph() empties the graph, the selection, the frames and the import records, but never resets uiStore.canvasLocked. Only the document load paths reset it (mcStore.applyRecommended: file import, share link, the desktop Templates menu, the phone's template menu). The lock is also kept in localStorage on purpose, so it survives a reload.
  • The same newGraph() is called by File → New, by starting a temporary session without carrying the document (switchToTemporary(false)), and by "Delete work data" in a personal browser (deleteWorkData).
  • The lock is enforced per control, not at the store: the canvas, the Inspector, the model panels, the Register expression field, the frames and the keyboard shortcuts read it; the toolbar palette, Insert module, the toolbar Undo / Redo buttons, the Data import and refresh wizards and Apply proposal do not.
  • e2e/canvas-lock.spec.ts seeds every case with newGraph() immediately followed by applyRecommended(...) through the dev bridge, the step the real New never takes, so no test reached the bug.

The contract (decided)

  • After File → New, after starting a temporary session without the document, and after Delete work data, the new document is always unlocked.
  • Every whole-document replacement is a document boundary: File → New, a template, an imported file and a share link. After it, the undo / redo history is empty, so Undo and Redo are disabled and Undo can never go back to the previous document. The confirmation dialog before the replacement is the safety net.
  • While locked, every user edit is blocked: palette add (click and drag), Insert module, drag, delete, cut, paste, duplicate, the Inspector, undo and redo, and every other command that changes positions, structure or data.
  • While locked, these stay available: selection, pan and zoom, Focus and the other view settings, run and Step (and the values they change), copy, export, share, and unlocking.
  • Focus mode is a global view setting and stays as it is across New.
  • The phone stays view-only, as today (docs/mobile.md §MV3a).

Every entry point that changes the document

Read from the code at c5e7d8f (every call of a document-changing graphStore action outside the store and the tests), and measured where marked.

Entry point Today, while locked After this fix
Palette click (toolbar) goes through (measured) blocked
Palette drag onto the canvas blocked (onDrop off) blocked
Insert module (menu) goes through (measured) blocked
Node drag, connect, reconnect blocked (React Flow props) blocked
Arrow-key node move blocked (nodes not draggable) blocked
Delete / Backspace blocked blocked
Inspector fields, Delete buttons, accent colour blocked (<fieldset disabled>) blocked
Inputs panel parameter values blocked blocked
Register expression field blocked blocked
Frames: draw, move, resize, rename, properties, Suggest frames blocked (hidden or not editable) blocked
Undo / Redo keys blocked blocked
Undo / Redo toolbar buttons goes through (measured) blocked
Data → import wizard (commitDataImport) goes through (code) blocked
Data → refresh (commitRefresh) and table rename goes through (code) blocked
Review → Apply proposal (projectStore.applyProposal) goes through (code) blocked
Copy, cut, paste, duplicate of nodes not features today if added later, copy stays allowed, the rest follow the lock
New, Templates, File import, share link replace the document; each sets the lock from its own document; each adds an undo step back to the previous document allowed while locked; New unlocked; the history is empty afterwards
Run, Step, Monte Carlo, seed, speed, Timeline series allowed allowed
Focus, Filters, Activity overlay, minimap, Pan mode, region select allowed allowed
Graph / Workspace / CSV export, share link creation, project revision export, Make a proposal allowed allowed (to confirm in the tests that none of them changes the document)

The fix guards the editor's mutation and history policy in one place that every user edit passes through, so a new entry point cannot forget the lock, and keeps the per-control disabling for what the user sees. That one place explicitly lets three things through: a whole-document replacement (New, a template, a file, a share link), the runtime updates of run, Step and Monte Carlo, and unlocking.

Tests

  • Through the real UI, from a locked document: File → New, Storage and privacy → a temporary session without the document, and Delete work data each leave an unlocked, empty document with Undo and Redo disabled, also after a reload.
  • After a template, an imported file and a share link are opened, Undo and Redo are disabled, and Ctrl+Z does not bring the previous document back.
  • While locked, each blocked entry point in the table leaves the content digest unchanged; each allowed one still works, run and Step included.
  • The existing lock tests keep passing; the seed that hid the bug is not the only path any more.

Out of scope

Related: #330 starts (v0.22.0) after this release and the v0.21.4 silhouette work.

Activity

  1. MerciHanrim commented on Oct 8, 2026

    @MerciHanrim
    OwnerAuthor

    Shipped in v0.21.3 by #336, together with #335, squash-merged as 70a9c37 on 2026-10-08 at 10:41 Seoul time (01:41 UTC). Its tree is identical to the approved pull request head fb7ad93.

    • Scope delivered: File → New, a temporary session started without the open diagram, and Delete work data leave an unlocked, empty document. A Template, a file, a share link and Open proposal as document open with their own lock and an empty undo history; the lock is written once, as its final value, before the new graph is set. While locked, every user edit is refused in the UI (the controls are disabled) and at the stores: the palette, Insert module, Undo and Redo, the data import wizard, a data refresh, renaming a bound table and a revision Apply. Selection, pan and zoom, Focus and the view settings, Run and Step, export and sharing, and unlocking stay available. The contract is in docs/canvas-edit-lock.md.
    • Pull request CI (run 37712395950 at fb7ad93, attempt 1): every job passed, the aggregate e2e job included; the five shards ran exactly the 2,030 listed tests, each once (2,023 passed, 7 skipped by design, 0 failed, 0 retried); the production bundle 16 of 16, the PWA 19 of 19.
    • Main CI (run 37714159715 at 70a9c37, attempt 1): every job passed, the aggregate e2e job included; the 2,030 listed tests each ran: 2,022 passed, 7 skipped by design, 0 failed, 1 flaky / 1 retry. The retried test, frame-label-locale-switch.spec.ts:142, is outside this change and passed without a retry on the same tree in the pull request run; it is recorded in test(e2e): the zh-Hant descriptive-copy wrapping test slows down on CI and ended a browser session #305. The aggregation exception recorded in fix(canvas): Pool, Register and Parameter value rows sit too close to node silhouettes #332 did not repeat.
    • Production: https://cozy-loop-studio.pages.dev serves v0.21.3 · build 70a9c37 (the desktop toolbar label and the phone's ⋯ menu). An automated check through the UI only, in fresh Chrome contexts, passed 41 of 41, including New, a temporary session and Delete work data from the locked Early MMO example (unlocked, empty, Undo and Redo off); a Template, a file and a share link each opening with their own lock and no history; every blocked control disabled with the document unchanged; the allowed actions working; no dev bridge and no page error.

    Out of scope, as decided in this issue: the naming of the rail's other toggles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions