Skip to content

FE-1586: Remember the canvas viewport per net - #9510

Merged
kube merged 4 commits into
mainfrom
claude/canvas-viewport-persistence
Sep 4, 2026
Merged

FE-1586: Remember the canvas viewport per net#9510
kube merged 4 commits into
mainfrom
claude/canvas-viewport-persistence

Conversation

@kube

@kube kube commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

Before this PR, the canvas opened every net fitted to the screen. Switching to another net and back, or reloading the app, lost the position and zoom the user had set.

Remembers the viewport per net in the user settings. The canvas starts from the saved viewport when one exists and reports its moves through one context, so a net reopens where it was left.

9510.mp4

Links

Changes

  • CanvasViewportContext exposes savedViewport and rememberViewport

    CanvasViewportProvider sits inside UserSettingsProvider and keys everything by the current petriNetId.

  • Each settled viewport is written as it is reported

    Renderers report once a move settles rather than every frame, so a reload straight after a move still comes back to it.

  • canvasViewports user setting keeps the most recently saved nets

    remember-canvas-viewport.ts stamps each entry and caps the map at 50 nets, evicting by that stamp.
    Persisted with the other settings.

  • Canvas reads the saved viewport at mount and otherwise fits the net

    React Flow reports moves through onMoveEnd.
    Any renderer behind the contract reads the same context.

  • CanvasViewport type moves to the React layer

    Renderer contract re-exports it.

Review fixes

  • Writes no longer coalesce, and the cap no longer reads key order

    A reload inside the 250 ms window lost the last move, and the flush ran on every render rather than on unmount.
    A document id that reads as an integer enumerates numerically, so the cap dropped the wrong net.

Known issues

  • Viewports of deleted nets stay until the cap evicts them

Test coverage

  • remember-canvas-viewport.test.ts:

    Replacement, the cap, eviction by save time, and document ids that read as integers.

  • Headless Playwright script:

    Pan and zoom, reload, restore, and a fresh fit for a second net.

How to test

  • Open Petrinaut preview on Vercel
  • Load example > SIR, zoom and pan
  • Reload
  • Expect same position and zoom
  • Load example > any other example
  • Expect fit to screen
  • Switch back to SIR
  • Expect previous position and zoom

@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
hash Ready Ready Preview Sep 4, 2026 2:19am UTC
petrinaut Ready Ready Preview Sep 4, 2026 2:19am UTC
petrinaut-docs Ready Ready Preview Sep 4, 2026 2:19am UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 4, 2026 2:19am UTC

Request Review

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team labels Sep 2, 2026
@kube
kube force-pushed the claude/canvas-viewport-persistence branch from c08b2dd to 5903e85 Compare September 3, 2026 23:16
@kube
kube force-pushed the claude/canvas-viewport-persistence branch from 5903e85 to 808cd87 Compare September 3, 2026 23:32
@kube
kube changed the base branch from claude/canvas-pixi-renderer to main September 4, 2026 01:36
@github-actions github-actions Bot added the area/deps Relates to third-party dependencies (area) label Sep 4, 2026
@kube
kube force-pushed the claude/canvas-viewport-persistence branch from 808cd87 to b163578 Compare September 4, 2026 01:37
@kube
kube changed the base branch from main to claude/canvas-renderer-interface September 4, 2026 01:37
@github-actions github-actions Bot removed the area/deps Relates to third-party dependencies (area) label Sep 4, 2026
@kube kube changed the title Remember the canvas viewport per document across net switches and reloads FE-1586: Remember the canvas viewport per net Sep 4, 2026
@kube
kube marked this pull request as ready for review September 4, 2026 01:39
@kube
kube requested review from CiaranMn and a balanced review from Copilot September 4, 2026 01:39
@cursor

cursor Bot commented Sep 4, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Localized editor UX and localStorage user-settings growth is bounded; no auth, simulation, or net-definition logic changes.

Overview
Adds per-net canvas viewport persistence so pan/zoom survive net switches and full page reloads; nets without a saved view still fit to screen on first open.

A new CanvasViewportProvider and CanvasViewportContext (savedViewport, rememberViewport) sit under UserSettingsProvider in PetrinautProvider, keyed by the active petriNetId. Viewports are stored in user settings as canvasViewports (with savedAt stamps) and written through setCanvasViewport / rememberCanvasViewport, which caps at 50 nets by evicting least-recently saved entries (including correct behavior when ids look like integers).

The React Flow canvas uses the saved viewport for defaultViewport at mount when present, and calls rememberViewport from onMoveEnd after pan/zoom settles. CanvasViewport is defined in the React state layer and re-exported from the renderer contract. Docs and a patch changeset describe the behavior.

Reviewed by Cursor Bugbot for commit e1e1f08. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI 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.

🟡 Changes recommended

Reload timing can lose a viewport, and numeric document IDs break recency-based eviction.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Persists each Petrinaut canvas viewport so nets reopen at their previous pan and zoom.

Changes:

  • Adds per-net viewport storage with capped retention.
  • Restores and records React Flow viewports.
  • Adds tests, documentation, and a patch changeset.
File summaries
File Description
.changeset/canvas-viewport-persistence.md Records the patch release.
docs/drawing-a-net.md Documents viewport restoration.
react/petrinaut-provider.tsx Mounts the viewport provider.
react/state/canvas-viewport-context.ts Defines the viewport contract.
react/state/canvas-viewport-provider.tsx Coordinates viewport persistence.
react/state/user-settings-context.ts Adds viewport settings and action.
react/state/user-settings-provider.tsx Updates persisted viewport state.
react/state/user-settings-provider/remember-canvas-viewport.ts Implements capped retention.
react/state/user-settings-provider/remember-canvas-viewport.test.ts Tests retention behavior.
ui/views/SDCPN/canvas-renderer.ts Re-exports the shared viewport type.
ui/views/SDCPN/renderers/react-flow/react-flow-canvas.tsx Restores and reports the viewport.
ui/views/Editor/panels/SimulateView/experiments/create-experiment-drawer.test.tsx Updates the settings test fixture.
Review details
  • Files reviewed: 12/12 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread libs/@hashintel/petrinaut/src/react/state/canvas-viewport-provider.tsx Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b163578. Configure here.

Comment thread libs/@hashintel/petrinaut/src/react/state/canvas-viewport-provider.tsx Outdated
kube added 3 commits September 4, 2026 04:01
Review feedback read the replacement as reframing every net on first load.
It does not: xyflow resolves a numeric padding to whole pixels a side and
subtracts them from the container, which is the same fit as dividing the
bounds by one plus the padding, to within that flooring.
Coalescing the writes left the last move in memory for 250 ms, so a reload
in that window lost it, and the flush ran on every render rather than on
unmount. Renderers report a settled viewport instead of each frame, so the
write goes straight through and there is nothing pending to lose.

The cap read recency off object key order, which JavaScript does not keep
for keys that read as integers, so a saved viewport now carries a stamp and
the cap evicts by it.
@kube
kube added this pull request to the merge queue Sep 4, 2026
Base automatically changed from claude/canvas-renderer-interface to main September 4, 2026 15:40
Merged via the queue into main with commit 4ff2e3d Sep 4, 2026
88 of 114 checks passed
@kube
kube deleted the claude/canvas-viewport-persistence branch September 4, 2026 15:40
@hash-release hash-release Bot mentioned this pull request Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

3 participants