Skip to content

fix(canvas): the edit lock resets at document boundaries and shows its state (v0.21.3) - #336

Merged
MerciHanrim merged 1 commit into
mainfrom
fix/edit-lock-boundaries
Oct 8, 2026
Merged

MerciHanrim merged 1 commit into
mainfrom
fix/edit-lock-boundaries

Conversation

@MerciHanrim

Copy link
Copy Markdown
Owner

Part of #334 and #335. Released as v0.21.3 (the release date is set to the actual merge day in Seoul before merging).

The bugs

The contract

The whole contract is in the new docs/canvas-edit-lock.md.

  • Document boundaries. File → New, a temporary session started without the open diagram, Delete work data, a Template (desktop and phone), a Graph / Workspace / Project revision file, a share link and Open proposal as document each start a new document: the undo history is empty, so Undo can never go back into the previous document (the confirmation before a replacement is the safety net), and the lock is the new document's own: a document with recommendedRunConfig.canvasLocked: true opens locked, every other one, a new empty document included, unlocked.
  • One replacement, one lock write. The new document's lock is written once, as its final value, in the same pass as the swap and before the new graph is set, so a locked Template or file never shows, renders or autosaves an unlocked moment. loadDoc takes a required mode with no default (document-boundary with the document's canvasLocked, or revision-apply), so a future caller cannot leave it out; loadGraph (a Template) takes canvasLocked.
  • While locked, every user edit is refused, in the UI (the controls are disabled) and at the stores: src/store/editPolicy.ts registers one guard on graphStore (it cannot import uiStore without a cycle), and the graph's editing actions, the saved-frame changes and a revision Apply return without any change (no state, no history, no autosave, no simulationRev).
  • What passes: a whole-document replacement, the runtime of Run, Step and Monte Carlo (their stores never call a document-changing action), and unlocking; selection, pan and zoom, Focus and the view settings, export and sharing stay available. onNodesChange / onEdgesChange keep select and dimensions changes, and a replace change that only flips selected (a panel's reveal through React Flow's setNodes).
  • The lock button. Unlocked: an open padlock with its shackle swung to the side, the free end 2 px clear of the body at 1×. Locked: the closed padlock with the rail's pressed tell (the same tint, inset ring and colour as Focus, at least 3 : 1 against the unlocked button; Highlight / HighlightText in forced colours). The accessible name is fixed, "Edit lock" (canvas.lock.name), aria-pressed carries the state, and the tooltip names the next action. The phone stays view-only, with no lock button.

Tests

  • src/store/editPolicy.test.ts: every refused action leaves the document, the frames, the history and the digest unchanged; selection, a selection-only replace, a Step, export and unlocking work.
  • src/store/graphStore.boundary.test.ts: each boundary empties the history; a revision Apply keeps one undo entry; the lock takes only its final value (a locked document never passes through unlocked).
  • e2e/document-boundary.spec.ts: through the real UI, File → New, a temporary session, Delete work data, a Template, a file and a share link.
  • e2e/canvas-lock.spec.ts: while locked the controls are disabled and change nothing, Tab never lands on them, a menu's arrow keys skip the disabled rows, Enter and Space on a disabled chip do nothing; selecting, zooming, Focus, Step and export still work.
  • e2e/lock-control.spec.ts: the 1× gap of the open icon and none for the closed one, measured on the rendered button; the fixed name, aria-pressed and the tooltip; the pressed tell in light and dark and Highlight in forced colours; the keyboard focus ring on the pressed button; and the six button baselines below.

Tests replaced, not deleted

Four test titles of main are replaced, because the behaviour they assert is the one this pull request removes: an Undo right after New, a file or a Template load brought the previous document back. Under the new contract that history no longer exists; kept as they were, these tests would either fail or, retitled to pass, claim something they no longer check.

Removed title (main) Replaced by
large-graph-readability › Import adds ONE undo entry, never one-per-frame; pure Suggest / Dismiss / Clear suggested add none … the same test, now "Import is a document boundary (#334): an empty history, never one entry per frame; pure Suggest / Dismiss / Clear suggested add none …" (the Suggest / Dismiss / Clear checks are unchanged)
module-label-localization › [P1] insert -> New -> Undo -> a locale switch still syncs the restored instance "#334 New, a file and a Template-style load are document boundaries: Undo cannot bring the inserted module back" (all three paths)
module-label-localization › [P1] insert -> loadDoc (Import) -> Undo -> … the same
module-label-localization › [P1] insert -> loadGraph (Template-style load) -> Undo -> … the same, and "[P1] insert -> a revision Apply (in place) -> Undo -> a locale switch still syncs the restored instance": the history-aware provenance check moves to the one whole-graph load that stays undoable

Other tests adjusted: a few that edit a locked example (the early MMO and gacha templates open locked) through the dev bridge now lift the lock first, as a user has to; a few that compared canUndo across an import now assert the history directly; storage-sessions uses the Focus preference, not the lock, as its "a preference survives Delete work data" example.

The list: 2,030 tests (main's 2,013, 21 added, 4 replaced as above), each in exactly one of the 5 shards, every shard within its budget.

Visual baselines

A full run on the final code passed every existing baseline, but that only means each difference was inside its tolerance: a shadow capture that painted only the lock button found it in 35 of the 70 baselines. 25 PNGs change, by approval:

  • 6 new, lock-{light,dark,forced}-{unlocked,locked}: the real 26 px button with its 14 px icon, captured from the product UI at 1×; the only captures of both states in every theme (forced colours locked existed nowhere).
  • 19 replaced, where the lock button is the only change: canvas-refresh-visual forced-colors-L2, matrix-dark-L0, matrix-dark-L2, matrix-light-L2; flow-colour-views-visual template coffee and gacha, light and dark; flow-colour-visual dark-L2, light-L2, states; large-graph-readability auto-frames, auto-frames-mixed, run-distinction-states; model-nodes-visual register-unit-row; playback-visual depart dark, light and forced colours, travel-L0.

Kept unchanged, byte for byte:

  • 9 that also differ outside the button (29–897 px at the L0 dot nodes and clip edges, not shown to come from this work): canvas-refresh-visual forced-colors-L0, matrix-dark-L1, matrix-light-L0, matrix-light-L1; flow-colour-visual dark-L0, light-L0; large-graph-readability frames-activity; flow-colour-views-visual template mmo light and dark.
  • 7 stale for several releases (they still show the emoji padlock from before v0.15.3 and older node outlines, inside the 0.5 % full-page tolerance): i18n-visual ko-desktop-app, ko-export-menu, ko-long-label-and-tip; large-graph-readability frame-colours, -dark, -forced, -overlap. A separate refresh, attributed against main, is left as a follow-up.

Release

  • Version 0.21.3, .changes/edit-lock-boundaries.json, release note release:0.21.3 with three lines in 18 languages, 16 without native review, plus the button's name; no node kind is named.
  • Copy guards move only their exact pins: catalog 1036 to 1040, runtime 1258 to 1262. Every new line reads the same in pt-BR and pt-PT, so pt-PT's difference stays at 277 (249 outside the password keys, against its quarter bound of 249.75); the es lines keep the usted register; no ru line carries ё. No bound is widened.
  • Docs: docs/canvas-edit-lock.md (new), docs/bundled-module-label-localization.md (rules 10 and 11 and MLS-D5 follow the boundary rule), CHANGELOG.md, README.md.

Verification (local, at the head commit)

  • npx tsc -b, oxlint (39 warnings, the existing baseline, 0 errors), 3,270 unit tests, all 21 source checks of the CI checks job; the web, portable and PWA builds with their closure and notice checks; git diff --check.
  • End to end in Chrome, without retries: the whole chromium and mobile projects on the final code before the baseline update, 2,004 passed, 0 failed, 7 skipped by design; after it, every screenshot spec plus lock-control on both projects twice in a row without an update, 330 passed, 5 skipped, both times.

Not claimed

  • No manual check of this pull request's preview, and no screen-reader check; the accessibility claims are the automated name, state, focus and keyboard checks above.

…s state (v0.21.3)

Part of #334 and #335.

A new document no longer inherits the previous document's edit lock: File -> New, a temporary session started without the open diagram, and Delete work data leave an unlocked, empty document. Every whole-document replacement (New, a Template, a file, a share link, Open proposal as document) is a document boundary: the undo history starts empty, and the new document's own lock is written once, as part of the swap, so a locked Template or file never shows an unlocked moment.

While locked, every user edit is refused, in the UI and at the stores. A new editPolicy module registers one guard on graphStore (it cannot import uiStore without a cycle); the graph's editing actions, the saved-frame changes and a revision Apply return without any change. Selection and size measurements still pass through onNodesChange / onEdgesChange, including a replace change that only flips `selected` (a panel reveal through setNodes). loadDoc takes a required mode, document-boundary or revision-apply, with no default; loadGraph takes the Template's lock. The palette, Undo / Redo, Insert module, the data import wizard, a data refresh, renaming a bound table and Review's Apply are disabled while locked. Run, Step, view settings, export and sharing stay available.

The lock button now reads at a glance without colour: an open padlock with its shackle swung to the side (a 2 px gap at 1x) when unlocked, and the closed padlock with the rail's pressed tell when locked (Highlight in forced colours). Its accessible name is fixed ("Edit lock", one new string in 18 languages), aria-pressed carries the state and the tooltip names the next action.

Tests: unit tests for the policy and the boundaries; e2e document-boundary and lock-control specs through the real UI; tests that relied on the removed "Undo restores the previous document" contract are replaced (4 titles), tests that edited a locked example through the dev bridge lift the lock first. Baselines: 6 new lock-button captures and 19 replaced ones whose only change is the lock button.

Docs: docs/canvas-edit-lock.md; docs/bundled-module-label-localization.md follows the boundary rule. Release 0.21.3 with three release-note lines in 18 languages; the copy guards move only their exact pins (pt-PT's difference stays 277).
@MerciHanrim MerciHanrim added the bug Something isn't working label Oct 8, 2026
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying cozy-loop-studio with  Cloudflare Pages  Cloudflare Pages

Latest commit: fb7ad93
Status: ✅  Deploy successful!
Preview URL: https://a5f21b37.cozy-loop-studio.pages.dev
Branch Preview URL: https://fix-edit-lock-boundaries.cozy-loop-studio.pages.dev

View logs

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant