Skip to content

FE-1585: Define the canvas renderer contract and make React Flow its first implementation - #9508

Merged
kube merged 2 commits into
mainfrom
claude/canvas-renderer-interface
Sep 4, 2026
Merged

FE-1585: Define the canvas renderer contract and make React Flow its first implementation#9508
kube merged 2 commits into
mainfrom
claude/canvas-renderer-interface

Conversation

@kube

@kube kube commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Important

Refactor, no user-facing change.

Summary

Before this PR, the canvas view was one React Flow component. Node and arc derivation, selection, drag, connection and placement rules and the viewport lived in React Flow callbacks and hooks. Overlays such as the viewport controls called React Flow's API directly, so a second renderer would have to copy all of it.

Splits the canvas into a renderer-agnostic scene, shared interaction semantics and a renderer contract. React Flow becomes the first renderer behind that contract, moved under its own folder. Behaviour is unchanged.

Links

Changes

Contract

  • CanvasScene describes what a renderer draws

    canvas-scene.ts builds it from the net and the editor state and replaces use-sdcpn-to-react-flow.
    Nodes carry position, size, selection, hover and dimming.
    Arcs carry kind, weight, ports and colour.

  • CanvasRenderer props and the CanvasController context

    A renderer receives the scene, the settled container size and host viewport actions.
    It publishes a controller with get and set viewport, zoom in and out, and screen to scene conversion.

  • Overlays talk to the controller instead of React Flow

    Viewport controls and the recenter-on-panel-open hook use useCanvasController.

  • Fit and recenter math without xyflow

    canvas-viewport.ts replaces ui/lib/viewport.ts with the same formulas.

  • Renderer registry keyed by name

    sdcpn-view.tsx measures the container, builds the scene and mounts the registered renderer.
    It remounts when the document or the renderer changes.

Shared interactions

  • One hook holds the editing semantics

    use-canvas-interactions.ts covers hover, selection changes, box selection, node drag with grid snapping, connection validation, click to place and toolbar drops.
    A renderer turns its own hit testing and gestures into these calls.

  • Toolbar drag payload shared by the bottom bar and renderers

    views/shared/canvas-node-drag.ts writes and reads the dragged node kind.

React Flow renderer

  • React Flow code moves under renderers/react-flow/

    Canvas component, node and edge components, scene to element mapping, node change application, controller, Monaco keyboard isolation and port handle ids.
    Declared as arch-docs layer ui.views.canvas.react-flow.

  • xyflow stylesheet import moves from petrinaut.tsx into the renderer

Review fixes

  • Fit parity with xyflow pinned by a test

    Review read the replacement as reframing every net on first load.
    xyflow floors a numeric padding to whole pixels a side and subtracts both, which is the same fit as dividing the bounds by one plus the padding.

Test coverage

  • canvas-scene.test.ts:

    Scene building from a net and editor state: nodes, arcs, dimming.

  • canvas-viewport.test.ts:

    Fit and recenter math, moved with the module from ui/lib/viewport.test.ts.

  • fit-viewport-parity.test.ts:

    The shared fit against xyflow's getViewportForBounds, across net shapes and at both ends of the zoom range.

  • Existing @hashintel/petrinaut unit suite and stories

How to test

  • Open Petrinaut preview on Vercel
  • Load example > SIR
  • Add places and transitions from the bottom toolbar, connect ports by drag, box select, move nodes with snap to grid
  • Simulate > Run, play back
  • Expect arcs to pulse and transitions to flash as on main
  • Zoom with viewport controls, open a side panel, toggle minimap
  • Expect same behaviour as on main

@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:23am UTC
petrinaut Ready Ready Preview Sep 4, 2026 2:23am UTC
petrinaut-docs Ready Ready Preview Sep 4, 2026 2:23am UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 4, 2026 2:23am UTC

Request Review

@kube
kube force-pushed the claude/canvas-renderer-interface branch from fc8f5fe to 273ca42 Compare September 3, 2026 23:16
@kube kube changed the title Define the canvas renderer contract and make React Flow its first implementation FE-1585: Define the canvas renderer contract and make React Flow its first implementation 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

Medium Risk
Large refactor of the primary net-editing surface (drag, connect, viewport, selection); behavior is intended to be unchanged but regressions would affect core editor UX.

Overview
Refactors the SDCPN canvas so drawing is separate from editing: a renderer-agnostic CanvasScene (via buildCanvasScene / useCanvasScene) replaces use-sdcpn-to-react-flow, and use-canvas-interactions centralizes selection, drag, connections, and placement that used to live in the monolithic sdcpn-canvas.

Adds a pluggable renderer layer: CanvasRenderer + CanvasController context let overlays (viewport controls, panel recenter) call zoom/pan/coordinate APIs without importing React Flow. sdcpn-view builds the scene and mounts the registry default (react-flow); the old root-level xyflow CSS import moves into the React Flow renderer folder.

React Flow is relocated under renderers/react-flow/ with thin adapters (use-react-flow-elements, use-apply-node-changes → shared interactions, use-react-flow-controller). Toolbar drags use canvas-node-drag instead of application/reactflow.

Viewport helpers move to canvas-viewport.ts with a custom fitViewportToBounds (replacing xyflow’s getViewportForBounds for initial fit) and parity tests; recenterToFitViewport now works on generic scene nodes.

Reviewed by Cursor Bugbot for commit 80132cc. 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

Renderer coupling and incomplete component-port validation remain unresolved.

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

Pull request overview

Refactors Petrinaut’s canvas behind renderer-neutral contracts, with React Flow as the first implementation and no intended user-facing changes.

Changes:

  • Adds shared scene, interaction, viewport, renderer, and controller abstractions.
  • Moves React Flow behind a renderer registry.
  • Adds scene and viewport tests plus shared toolbar drag payloads.
File summaries
File Description
libs/@hashintel/petrinaut/src/ui/views/shared/canvas-node-drag.ts Defines shared toolbar drag payloads.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/use-canvas-scene.ts Builds the active canvas scene.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/use-canvas-interactions.ts Centralizes interactions; component-port validation and interaction tests need improvement.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/sdcpn-view.tsx Measures the container and mounts the registered renderer.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/sdcpn-canvas.tsx Removes the monolithic canvas implementation.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/use-react-flow-elements.ts Maps scenes to React Flow elements; renderers still recompute scene state from editor context.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/use-react-flow-controller.ts Adapts React Flow viewport APIs.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/use-monaco-keyboard-isolation.ts Isolates Monaco keyboard events.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/use-apply-node-changes.ts Adapts React Flow node changes.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/transition-node.tsx Relocates transition rendering.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/react-flow-types.ts Defines renderer-specific element types.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/port-handles.ts Centralizes port-handle translation.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/place-state-tooltip.tsx Relocates place-state tooltip rendering.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/place-node.tsx Relocates place rendering.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/node-card.tsx Relocates shared node-card rendering.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/mini-map.tsx Adapts the minimap to scene node kinds.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/component-instance-node.tsx Uses centralized component port IDs.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/classic-transition-node.tsx Relocates classic transition rendering.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/classic-place-node.tsx Relocates classic place rendering.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas/arc.tsx Adapts arcs to scene arc kinds.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/renderers/react-flow/react-flow-canvas.tsx Implements the React Flow renderer.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/reactflow-types.ts Removes legacy React Flow types.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/hooks/use-sdcpn-to-react-flow.ts Removes legacy element derivation.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/hooks/use-recenter-on-panel-open.ts Uses the renderer-neutral controller.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/hooks/use-apply-node-changes.ts Removes legacy change handling.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/components/viewport-controls.tsx Uses the canvas controller.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/canvas-viewport.ts Implements renderer-neutral viewport math.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/canvas-viewport.test.ts Tests fitting and recentering behavior.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/canvas-scene.ts Defines and derives canvas scenes.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/canvas-scene.test.ts Tests scene derivation.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/canvas-renderers.ts Registers available renderers.
libs/@hashintel/petrinaut/src/ui/views/SDCPN/canvas-renderer.ts Defines renderer and controller contracts.
libs/@hashintel/petrinaut/src/ui/views/Editor/components/BottomBar/toolbar-modes.tsx Writes shared drag payloads.
libs/@hashintel/petrinaut/src/ui/petrinaut.tsx Removes the global xyflow stylesheet import.
libs/@hashintel/petrinaut/src/ui/lib/viewport.test.ts Removes superseded viewport tests.
Review details

Suppressed comments (1)

libs/@hashintel/petrinaut/src/ui/views/SDCPN/use-canvas-interactions.ts:115

  • useCanvasInteractions now defines the editing behavior shared by every renderer, but it has no tests; the SDCPN tests only cover scene and viewport derivation. Regressions in connection direction/port validation or snap-and-commit behavior would affect every implementation without being caught. Add hook tests for valid and invalid connections, snapped move/drop commits, and placement/read-only paths.
  • Files reviewed: 35/35 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.

@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 273ca42. Configure here.

Comment thread libs/@hashintel/petrinaut/src/ui/views/SDCPN/canvas-viewport.ts
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.
@kube
kube added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit a9ad96c Sep 4, 2026
74 checks passed
@kube
kube deleted the claude/canvas-renderer-interface branch September 4, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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