Without a "test" script the package was invisible to `turbo test` —
private-editor's CI runs `bun run test` (= `turbo test`), which only
walks workspace packages that declare a test runner. Mirrors mcp and
nodes which already do this.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Before mounting any legacy build tool, ToolManager checks whether the
active tool's kind has a registered NodeDefinition with a tool
contribution. If yes, the registry tool wins; the legacy tool map and
special-cased spawn/column/elevator branches are skipped for that kind.
Lazy-loaded via React.lazy (cached by loader) and wrapped in Suspense.
Today the registry is empty, so useRegistryTool is always false and
every code path renders unchanged. The moment a kind registers (Phase
2+), its registry tool takes over without further edits here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two additions plus a viewer JSX rewire:
- legacy-system.tsx: <LegacySystem kind="..."> wrapper that renders its
children only when nodeRegistry.has(kind) is false. Lets one wrapper
cover all legacy systems for a kind (door has DoorSystem and
DoorAnimationSystem — both belong to 'door' so they yield together).
- registered-systems.tsx: <RegisteredSystems /> iterates the registry,
filters entries that contribute a system, sorts by system.priority
(default 5; e.g. wall mitering at 8 runs after door cuts at 3),
mounts each via React.lazy. Today empty registry = renders nothing.
- viewer/index.tsx: every existing per-kind system is wrapped in
LegacySystem. RegisteredSystems is mounted alongside.
With the registry empty (Phase 0), every LegacySystem passes through
unchanged and RegisteredSystems is a no-op — zero behavior change.
Once a kind registers in Phase 2+, its legacy systems yield and its
registry-contributed system runs in their place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NodeRenderer now checks the registry first. Registered kinds load
their renderer module via React.lazy (cached by RendererSource so the
Suspense boundary is stable across re-renders). Unregistered kinds
fall through to the legacy chain below.
Today the registry is empty (Phase 0 builtinPlugin.nodes is []), so
every node still hits the legacy chain — no behavior change. The
moment a kind registers in @pascal-app/nodes (Phase 2 onward), the
registry path takes over without further edits here.
GLB / instanced-GLB RendererSource kinds are typed but not yet
honored — they get their built-in renderers in Phase 5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
byType was a hardcoded object keyed by the built-in node kinds. With
the registry, kinds can come from @pascal-app/nodes (or future
plugins) — so byType now wraps a Map via a Proxy that auto-creates an
empty Set the first time any kind is touched.
Built-in kinds are still pre-seeded at module init so the fast path
(no Proxy trap) is preserved. clear() iterates the backing Map.
useRegistry's `type` parameter widens from `keyof typeof byType` to
`KnownNodeKind | (string & {})` — preserves autocomplete for
built-ins while accepting plugin-supplied kinds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds apps/editor/lib/bootstrap.ts that calls loadPlugin(builtinPlugin)
as a module-side-effect on first import. Imported from scene-loader.tsx
so it runs on the client side where the editor mounts.
Idempotency guard handles HMR re-execution (would otherwise throw on
duplicate registerNode). For the empty plugin this commit, the entire
call is a no-op — included now so future commits that add real node
kinds only need to push them onto builtinPlugin.nodes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion changes for the new nodes package: bun.lock entry from
`bun install`, and a Biome-auto-sort of the registry barrel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New workspace package that owns built-in node bundles, one folder per
kind. Today the plugin is empty — no behavior change. Future commits
will add column/, shelf/, wall/, etc. and append each definition to
builtinPlugin.nodes.
The package depends on core (registry types) at v1; viewer, editor,
react, three are declared as peer deps so future node bundles can use
them without bumping their own version on every monorepo bump.
External plugins land as separate packages with the exact same shape
— this package is the dogfooded reference.
Tests: builtinPlugin shape + loadPlugin succeeds with zero kinds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a Biome noRestrictedImports rule scoped to core/, viewer/, and
editor/ packages. Framework code must reach node-specific behavior via
nodeRegistry.get(kind), never via direct import. The @pascal-app/nodes
package doesn't exist yet (lands in the next commit) so the rule is a
no-op today; codifies the boundary ahead of node bundles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the @pascal-app/core/registry surface that future node-bundle
packages (and external plugins) will use to register node kinds with the
host. No runtime behavior changes — registry is empty until subsequent
PRs populate it.
- types.ts: NodeDefinition, Capabilities, Relations, DragAction, Plugin,
ParametricDescriptor, Affordance, SceneApi, NodeRegistry. Capability
configs accept an override escape hatch; additive-only after v1.
- registry.ts: nodeRegistry singleton, registerNode, async loadPlugin.
Validates kind, schemaVersion, apiVersion; rejects duplicate kinds.
- scene-api.ts: createSceneApi factory wrapping the scene store with
copy-on-write snapshot semantics for pauseHistory/restore/resumeHistory.
- index.ts: barrel re-exporting the public surface.
- core/index.ts + package.json: export * from registry and add the
./registry subpath so consumers can import either way.
Tests (27 cases, all bun:test): registry registration / validation /
plugin loading; SceneApi read/write/dirty/history; lazy snapshot capture
with update/upsert/delete reversal via restore and restoreAll.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The file imports useEffect/useRef from React, which Next.js RSC builds
flag as client-only. Other core systems (e.g. elevator-runtime-system)
use useFrame from @react-three/fiber and slip through, but this one
needs the directive explicitly.
Fixes Turbopack build failure in private-editor community app:
"You're importing a module that depends on useEffect into a React
Server Component module."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace per-tool rule trees (.cursor/rules, .claude/rules, .codex/rules)
with a single wiki/architecture/ source — 11 pages + README — readable
as plain markdown by any agent. Canonical skills live in .agents/skills/;
.claude/skills, .cursor/skills, .codex/skills are directory symlinks.
AGENTS.md is the entrypoint (rewritten as a lean overview, no per-tool
path lists). CLAUDE.md, GEMINI.md, and .github/copilot-instructions.md
all point to it.
Add open-pr skill (uses .github/pull_request_template.md as the source
of truth for the PR body) and remove the dangling .claude/CLAUDE.md
relative symlink.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>