Skip to content

perf(web): memo FlowNode + GPU-layer sprites#117

Merged
naorsabag merged 2 commits into
masterfrom
perf/memo-flownode-and-gpu-sprite
May 10, 2026
Merged

perf(web): memo FlowNode + GPU-layer sprites#117
naorsabag merged 2 commits into
masterfrom
perf/memo-flownode-and-gpu-sprite

Conversation

@naorsabag
Copy link
Copy Markdown
Owner

@naorsabag naorsabag commented May 10, 2026

The 61-node `type-variants` showcase flow felt sluggish on pan/zoom. Two root causes, both fixed here.

1. `FlowNodeComponent` was not memoized

React Flow doesn't auto-memo custom node components, so every viewport store-update from pan/zoom re-renders every visible node. With 60+ nodes the reconciler churn dominates the frame budget.

`memo()` short-circuits on shallow-equal `data`. FlowCanvas's `nodes` useMemo already keeps `data` references stable across viewport changes (its deps list excludes viewport state), so this lands correctly.

2. SVG sprites re-rasterize per zoom level

`image-rendering: pixelated` keeps it nearest-neighbor after rasterization, but with a vector source the browser still walks the SVG at each new output size. The largest sprites are heavy in source form (k8s 368 KB, scheduler 339 KB, docker 313 KB).

Adds `will-change: transform; transform: translate3d(0, 0, 0)` to the sprite wrapper. The rasterized image is promoted to its own compositor layer and cached; subsequent zoom transforms become pure compositor work — no CPU re-raster per frame.

Downsides (you asked)

memo:

  • Adds a shallow-compare on each render (microseconds; not measurable).
  • Won't help during playback, where most node props change every step. This is purely a pan/zoom optimization, which is the use case you reported.
  • Zero correctness risk — the component is pure.

GPU layer per sprite:

  • VRAM cost: 60 layers × 108×108 RGBA ≈ 2.8 MB. Negligible on any modern device.
  • Higher first-paint cost (each layer needs creation); imperceptible on the scales we have.
  • Chrome's soft cap for compositor layers is ~125 layers/document. We're at ~60, well under.
  • Pixel-art with `image-rendering: pixelated` is unaffected by the compositor's subpixel handling — pixel-perfect stays pixel-perfect.

Test plan

  • `npm test` (42/42)
  • `npm run typecheck`
  • After deploy: open the type-variants flow, pan/zoom — should feel smooth at 60 fps

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Performance
    • Optimized flow node component rendering to reduce unnecessary re-renders during pan and zoom interactions.
    • Enhanced sprite rendering performance through CSS optimization for smoother visual updates.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f09e8d6-2db4-41ec-99b2-42b3897f6d95

📥 Commits

Reviewing files that changed from the base of the PR and between d8f0d67 and d7799ed.

📒 Files selected for processing (2)
  • packages/web/src/components/nodes/FlowNode.tsx
  • packages/web/src/components/nodes/NodeBuilding.tsx
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/components/nodes/NodeBuilding.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/components/nodes/FlowNode.tsx

Walkthrough

This PR optimizes React Flow node rendering by memoizing the FlowNodeComponent to prevent unnecessary re-renders during viewport pan/zoom operations, and promotes the SpriteBuilding sprite to a GPU compositor layer using CSS willChange and translate3d transforms for cached rasterization.

Changes

Web Component Rendering Optimization

Layer / File(s) Summary
React memo Import
packages/web/src/components/nodes/FlowNode.tsx
memo is added to React imports.
FlowNode Component Implementation
packages/web/src/components/nodes/FlowNode.tsx
Component logic is extracted into non-exported FlowNodeComponentInner function.
FlowNode Memoization Wrapper
packages/web/src/components/nodes/FlowNode.tsx
FlowNodeComponent is exported as memo(FlowNodeComponentInner) with explanatory comment documenting memoization behavior during React Flow viewport updates.
GPU-Accelerated Sprite Positioning
packages/web/src/components/nodes/NodeBuilding.tsx
SpriteBuilding wrapper div style expanded to add willChange: 'transform' and transform: 'translate3d(0, 0, 0)' for GPU compositor layer promotion.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • naorsabag/openhop#90: Directly modifies the same FlowNode.tsx file with label rendering and title behavior changes, while this PR wraps the component in React.memo.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the two main performance optimizations: memoizing FlowNode and GPU-layer optimization for sprites.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/memo-flownode-and-gpu-sprite

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli/src/init.ts`:
- Around line 341-346: The fallback hint is shown even when Tier‑1 clients exist
but are already installed; update the conditional that writes the message
(currently checking opts.json, installed, and wouldInstall) to also require that
no Tier‑1 targets were detected — e.g., add a check against the detection
collection used elsewhere in this module (look for the variable that holds
detected Tier‑1 targets, such as detectedTier1Targets / detectedTier1Clients /
similar) so the if becomes: if (!opts.json && installed.length === 0 &&
wouldInstall.length === 0 && detectedTier1Targets.length === 0) { ... } ensuring
the hint only appears when there are truly no detected Tier‑1 targets.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 182992e6-e4bc-49fb-9a50-4208113d2ec3

📥 Commits

Reviewing files that changed from the base of the PR and between f12e7d9 and d8f0d67.

📒 Files selected for processing (3)
  • packages/cli/src/init.ts
  • packages/web/src/components/nodes/FlowNode.tsx
  • packages/web/src/components/nodes/NodeBuilding.tsx

Comment thread packages/cli/src/init.ts
The 61-node type-variants showcase flow felt sluggish on pan/zoom.
Two root causes; both fixed here.

1. FlowNodeComponent wasn't memoized. React Flow doesn't auto-memo
   custom node components, so every viewport tick from
   pan/zoom (which fires store updates) re-renders all visible nodes.
   With 60+ nodes that reconciler churn dominates the frame budget.

   Wrap the inner function in React.memo. FlowCanvas's `nodes` useMemo
   already keeps `data` references stable across viewport changes
   (its deps exclude viewport state), so the shallow-equal short
   circuit kicks in.

2. SVG sprites re-rasterize per zoom level. `image-rendering: pixelated`
   keeps it nearest-neighbor *after* rasterization, but with vector
   source the browser still walks the SVG DOM at each new size. On the
   type-variants flow the larger sprites (k8s_node 368 KB, scheduler
   339 KB, docker 313 KB) are noticeable.

   Add `will-change: transform; transform: translate3d(0,0,0)` to the
   sprite wrapper so it's promoted to its own compositor layer. The
   rasterized image is cached once; subsequent zoom transforms become
   pure compositor work — no CPU re-raster per frame.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@naorsabag naorsabag force-pushed the perf/memo-flownode-and-gpu-sprite branch from d8f0d67 to d7799ed Compare May 10, 2026 18:52
@naorsabag naorsabag merged commit e01f7e7 into master May 10, 2026
8 checks passed
naorsabag added a commit that referenced this pull request May 10, 2026
…test

- @openhop/server: 0.1.0-beta.2 → 0.1.0
- @openhop/web:    0.1.0-beta.4 → 0.1.0
- openhop CLI:     0.1.0-beta.6 → 0.1.0
  + bump pinned @openhop/server, @openhop/web deps + hardcoded --version

publish.yml: drop `--tag beta` from all three publish steps. With no
--tag, `npm publish` sets the `latest` dist-tag — so npmjs.com pages
and `npm install <pkg>` (no @beta suffix) automatically reflect the
new version on every successful publish. No more manual
`npm dist-tag add latest` step.

Bundles 13 PRs since v0.1.0-beta.6:

Web (heavy):
- #106 sprite URLs use Vite BASE_URL (Pages 404 fix)
- #107 example flow cards on empty state
- #108 sidebar + carrot-click highlight for string-data steps
- #109 all 5 examples grouped under examples/ + first-visit autoload
- #112 per-step auto-zoom + playback speed button
- #113 collapse FLOWS / INSPECT by default on mobile
- #114 lazy editor + vendor-split chunks + meta description
- #115 filter codemirror from <modulepreload>
- #117 memo FlowNode + GPU-layer sprites for smoother pan/zoom

CLI:
- #116 OpenSkills fallback hint when no Tier-1 client gets the skill
- #118 suppress the hint when Tier-1 already has the skill

Server: untouched code; bumped to keep the 0.1.0 set consistent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
naorsabag added a commit that referenced this pull request May 11, 2026
…test (#119)

- @openhop/server: 0.1.0-beta.2 → 0.1.0
- @openhop/web:    0.1.0-beta.4 → 0.1.0
- openhop CLI:     0.1.0-beta.6 → 0.1.0
  + bump pinned @openhop/server, @openhop/web deps + hardcoded --version

publish.yml: drop `--tag beta` from all three publish steps. With no
--tag, `npm publish` sets the `latest` dist-tag — so npmjs.com pages
and `npm install <pkg>` (no @beta suffix) automatically reflect the
new version on every successful publish. No more manual
`npm dist-tag add latest` step.

Bundles 13 PRs since v0.1.0-beta.6:

Web (heavy):
- #106 sprite URLs use Vite BASE_URL (Pages 404 fix)
- #107 example flow cards on empty state
- #108 sidebar + carrot-click highlight for string-data steps
- #109 all 5 examples grouped under examples/ + first-visit autoload
- #112 per-step auto-zoom + playback speed button
- #113 collapse FLOWS / INSPECT by default on mobile
- #114 lazy editor + vendor-split chunks + meta description
- #115 filter codemirror from <modulepreload>
- #117 memo FlowNode + GPU-layer sprites for smoother pan/zoom

CLI:
- #116 OpenSkills fallback hint when no Tier-1 client gets the skill
- #118 suppress the hint when Tier-1 already has the skill

Server: untouched code; bumped to keep the 0.1.0 set consistent.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@naorsabag naorsabag deleted the perf/memo-flownode-and-gpu-sprite branch May 15, 2026 16:00
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.

1 participant