Skip to content

fix(viewer): render plugin nodes whose kind registers after mount - #925

Open
Aymericr wants to merge 1 commit into
mainfrom
fidelity/a-09-late-plugin-render
Open

Aymericr wants to merge 1 commit into
mainfrom
fidelity/a-09-late-plugin-render

Conversation

@Aymericr

@Aymericr Aymericr commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

A host discovers plugins asynchronously, so a scene can mount before its plugin kinds register. NodeRenderer read nodeRegistry.get(node.type) at render time and never subscribed to the registry. A plugin node mounted before its plugin registered rendered null and kept doing so after registration, until something unrelated re-rendered it. Geometry-only plugin kinds never mounted their group either, so GeometrySystem never built them.

NodeRenderer now subscribes, through useSyncExternalStore(onRegistryChange, …), to its own kind's definition only. A registration re-renders the nodes of that kind and leaves every other mounted node alone. There is no API change.

Failing case: it is already on main as test.failing in plugin-dispatch.test.tsx (added by #917): "nodes mounted before their plugin registers render once it registers" (Expected: 1, Received: 0). This PR flips it to test, so the duplicate coverage lives in one place. It also adds "registering an unrelated kind does not re-render mounted plugin nodes".

Found by the plugin API v1 conformance work (A-09).

How to test

  1. bun test packages/viewer/src/components/viewer/plugin-dispatch.test.tsx → 5 pass
  2. Remove the subscription → the late-registration test fails. Subscribe to the global registry version instead → the no-re-render test fails.
  3. bun run --cwd packages/viewer test → 370 pass, 0 fail. bun run check is clean and the viewer typechecks.

Local preview

Scene: /private/tmp/fid/previews/A-09/plugins-installed.json. It has a house, a Spielberg "Shot camera" and three Nature trees, with both plugins installed.
Start: /private/tmp/fid/preview.sh start A-09R /private/tmp/fid/previews/A-09/plugins-installed.json. Worktree /private/tmp/fid/A-09R: the editor pin plus the two #925 commits cherry-picked.

What you will notice

  • When a plugin's code arrives after the scene has opened, its nodes now appear as soon as the plugin loads. Before this fix they stayed invisible for the rest of the session. Here, the Spielberg shot camera on the house's north wall stays missing on MAIN and appears after the fix.
  • Unchanged: on a normal load the plugins usually register before the scene mounts, so nothing differs. before-1.png and after-1.png are identical.

Where to look

  • Project "Preview A-09R: plugins-installed", 3D view, level "Ground floor". The Shot camera is the small dark body on top of the far wall, right of centre.

Try this

  1. Open the project normally. The camera shows on both builds.
  2. Simulate a slow plugin load: in DevTools, throttle the network or block packages_plugin-spielberg_src_* chunks for about 15 s, then reload.
  3. MAIN :3100: the camera never appears, even after the chunk arrives. Preview :3300: it appears as soon as the chunk arrives.

Before / after (same view, Spielberg chunks held back 15 s by the capture script, screenshot at 45 s)

  • Before (MAIN :3100): /private/tmp/fid/previews/A-09R/before-2-late.png. The camera is missing (boxed).
  • After (:3300): /private/tmp/fid/previews/A-09R/after-2-late.png. The camera is back (boxed).
  • Normal load, no difference: before-1.png and after-1.png.
  • Capture script: /private/tmp/fid/scratch/A-09/shot-editor.ts with DELAY_PLUGIN_CHUNKS=spielberg.

Known limits

  • The late load is simulated by delaying the plugin's chunks in the browser. In production this happens when a plugin chunk is slow or retried after the scene loads.
  • Nature trees don't show on either server in this preview: the preview server runs without NEXT_PUBLIC_PLUGIN_TREES, so the Nature plugin isn't loaded. Only the Spielberg camera is used.

R1–R9

Rule Check
R1 Existing plugins keep rendering even when they register after the scene mounts. API unchanged
R2 Per-kind subscription: a registration re-renders only the nodes of that kind, once, never per frame (tested)
R3 Unaffected (no references or schema)
R4 Unaffected (no zones or space detection)
R5 Unaffected (no edit tools; selection and hover already re-derive on registration)
R6 Unaffected (baked viewer restore paths unchanged)
R7 Unaffected (no definitions or recipes)
R8 Unaffected (no tools, MCP or kernel)
R9 Unaffected (no capture or import)

Checklist

  • I've tested this locally with bun dev (see Local preview)
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable) (not needed)
  • This PR targets the main branch

🤖 Generated with Claude Code


Note

Medium Risk
Touches core viewer render path for every node; behavior change is narrow (late plugin registration) but high visibility if subscriptions or snapshot reads regress.

Overview
Fixes plugin nodes staying invisible when the scene mounts before async plugin discovery registers their kind. NodeRenderer no longer does a one-shot nodeRegistry.get at render time; it subscribes with useSyncExternalStore(onRegistryChange, …) and reads the definition for that node’s kind only, so custom renderers and geometry-only kinds appear once the plugin loads.

Tests promote the former test.failing late-registration case to a passing test, add render-count instrumentation, and assert that registering an unrelated kind does not re-run already-mounted plugin renderers.

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

@pascal

pascal Bot commented Sep 25, 2026

Copy link
Copy Markdown

I hit an error while handling your request (Model unavailable on AI Gateway free tier: Free tier users do not have access to this model. Upgrade to paid credits at https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dtop-up for unrestricted…).

Please try again, rephrase, or reach out if it keeps failing.

Error id: ff399ff5-d2a5-4414-beaf-9548f766fc0b

NodeRenderer read the registry at render time without subscribing, so nodes
mounted before async plugin discovery stayed invisible and unbuilt. It now
subscribes to its own kind's definition only, so a registration re-renders
the nodes of that kind and no others. Flips the plugin-dispatch
test.failing case recorded in #917 and adds a test that an unrelated kind's
registration does not re-render mounted nodes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Aymericr
Aymericr force-pushed the fidelity/a-09-late-plugin-render branch from 0332e7b to c5b69bb Compare September 25, 2026 10:23

This branch has not been deployed

No deployments
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