Skip to content

apps/editor: mount node-registry bootstrap at the root layout#317

Merged
wass08 merged 1 commit into
mainfrom
feat/client-bootstrap-provider
May 20, 2026
Merged

apps/editor: mount node-registry bootstrap at the root layout#317
wass08 merged 1 commit into
mainfrom
feat/client-bootstrap-provider

Conversation

@wass08
Copy link
Copy Markdown
Collaborator

@wass08 wass08 commented May 20, 2026

What does this PR do?

Fixes a registry-not-loaded bug in the standalone editor app, mirroring the community-side fix that landed in pascalorg/private-editor#27.

lib/bootstrap.ts was previously imported as a side effect only from components/scene-loader.tsx, so:

  • Every page in apps/editor other than the scene loader (homepage, settings, viewer-only routes) hit <Viewer> with an empty client registry → node materials resolved to null.
  • Even on the scene-loader page, the bootstrap call was async (await loadPlugin(builtinPlugin)), so the first SSR / hydration pass saw an empty registry — React surfaced the well-known <html>-level hydration mismatch on first paint.

Changes

  • New apps/editor/app/client-bootstrap.tsx — thin client wrapper that imports ../lib/bootstrap and renders children.
  • apps/editor/app/layout.tsx — mounts <ClientBootstrap> around {children} so every page gets the registry populated before its first <Viewer> / <Editor> mounts.
  • apps/editor/lib/bootstrap.ts — switched to synchronous built-in registration via registerNode(def) per kind (no more await loadPlugin(...) in the hot path). External plugin discovery (discoverPlugins()) stays async and runs via its own loadExternalPlugins() path, gated by externalsKickedOff so HMR doesn't re-fetch.
  • apps/editor/components/scene-loader.tsx — drops the per-page side-effect import; root provider handles it now.
  • bun.lock — syncs @pascal-app/editor into @pascal-app/nodes's peerDependencies + devDependencies (already declared in packages/nodes/package.json; only the lockfile lagged).

How to test

  1. bun install && bun dev from apps/editor.
  2. Open the homepage and any viewer-only routes (not just the scene loader) — nodes should render with materials, no black canvas.
  3. Browser console should show [pascal:registry] loaded @pascal-app/nodes-builtin v… (20 kinds: …) on first paint.
  4. No <html>-level hydration warning. (Note: the OneSec / similar browser-extension warning about one-sec-browser-extension-id is independent — that's the extension stamping <html> before React loads. React even calls this case out in the warning text.)
  5. Save / load a scene through the editor route — same behaviour as before, just without the per-page bootstrap import.

Screenshots / screen recording

N/A — fixes a render-bug, no visual surface added. Verification is "nodes show up everywhere" instead of "blank canvas off the scene-loader route."

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (pre-commit biome ran clean)
  • I've updated relevant documentation (if applicable) — inline doc comments updated in each file
  • This PR targets the main branch

The standalone editor app loaded `lib/bootstrap.ts` as a side-effect
import only from `components/scene-loader.tsx`. That worked for the
`/edit/[sceneId]` route but left every other page (homepage, settings,
viewer-only routes) hitting `<Viewer>` with an empty client-side
registry — node materials resolved to `null` and React surfaced a
`<html>`-level hydration mismatch on first paint.

Fix mirrors the community-app side that landed in pascalorg/private-
editor#27:

 - New `app/client-bootstrap.tsx` — thin client wrapper that imports
   `../lib/bootstrap` and renders children.
 - `app/layout.tsx` mounts `<ClientBootstrap>` around `{children}` so
   every page in the standalone editor gets the registry populated
   before its first `<Viewer>` / `<Editor>` mounts.
 - `lib/bootstrap.ts` switched to **synchronous** built-in registration
   via `registerNode(def)` per kind instead of `await loadPlugin(...)`.
   The previous async kick-off only resolved in a microtask, letting
   the first SSR / hydration pass see an empty registry. External
   plugin discovery (`discoverPlugins()`) stays async and runs via its
   own `loadExternalPlugins()` path, gated by `externalsKickedOff` so
   HMR doesn't re-fetch.
 - `components/scene-loader.tsx` drops the per-page side-effect import
   — the root provider handles it now.

`bun.lock` syncs `@pascal-app/editor` into `@pascal-app/nodes`'s
peerDependencies + devDependencies (already declared in
`packages/nodes/package.json`; only the lockfile lagged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mintlify
Copy link
Copy Markdown

mintlify Bot commented May 20, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
pascal 🔴 Failed May 20, 2026, 11:18 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@wass08 wass08 merged commit 70100d5 into main May 20, 2026
1 of 2 checks passed
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