Selection: registry-driven, drop spawn flag, restore green color

Two concerns from the spike:

1) Selection / floating-action-menu had hardcoded kind lists scattered
   across 4 files. Adding 'shelf' to each one per migration was the
   wrong abstraction — the user's question "did you make it generic
   from the noderegistry?" was the right one. Done now.

   Added to @pascal-app/core/registry:
   - getSelectableKinds(): string[] — returns all registered kinds
     whose definition declares `capabilities.selectable`.
   - isRegistrySelectable(kind): boolean — predicate for OR-chains.

   Refactored hardcoded sites to merge registry kinds at runtime,
   keeping legacy hardcoded lists intact so existing kinds keep
   working unchanged:
   - editor SelectionManager: 4 subscription loops (enter/leave/click)
     + structure.isValid + getSelectionTarget — all augment with
     registry kinds. Phase 6 deletes the hardcoded lists.
   - viewer SelectionManager: subscription loop + SelectableNodeType
     broadened with `(string & {})` to accept registry kinds.
   - floating-action-menu: ALLOWED_TYPES OR'd with isRegistrySelectable.
   - Removed the manually-added 'shelf' entries from previous commit
     857ddd4; they were redundant once the registry-driven path landed.

   Future built-in nodes that declare `capabilities.selectable` get
   click-selection + hover + the floating action menu (move/delete
   icons) for free, no editing of these 4 files.

2) Spawn parity is signed off. Drop the
   NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN flag entirely; spawn registers
   unconditionally in builtinPlugin.nodes. Restored SPAWN_COLOR to
   the original #22c55e green (was #ef4444 red as a Phase 2
   verification marker).

Pre-existing typecheck errors in editor (ceiling/fence/slab tree-node,
scene.ts buildingId) are unchanged.

630 tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-14 15:22:51 -04:00
co-authored by Claude Opus 4.7
parent 857ddd4d95
commit 6d97a87547
7 changed files with 119 additions and 67 deletions
+11 -33
View File
@@ -2,52 +2,30 @@ import type { AnyNodeDefinition, Plugin } from '@pascal-app/core'
import { shelfDefinition } from './shelf'
import { spawnDefinition } from './spawn'
/**
* Feature flag for the Phase 2 spike. When `NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN`
* is truthy, spawn registers through the registry path; otherwise the legacy
* `SpawnRenderer` and `SpawnTool` in viewer/editor packages own the kind.
*
* Removed in the PR that signs off parity (legacy spawn files deleted in the
* same commit). All other built-in node migrations follow the same pattern.
*/
function isSpawnRegistryEnabled(): boolean {
// Next.js / Turbopack inlines `process.env.NEXT_PUBLIC_*` references at
// build time, but ONLY when the access is a literal property — dynamic
// bracket access (`env[name]`) is not substituted and resolves to
// undefined in the browser. Keep this as a literal so the value is baked
// into the client bundle.
if (typeof process === 'undefined') return false
const flag = process.env.NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN
return flag === '1' || flag === 'true'
}
function getBuiltinNodes(): AnyNodeDefinition[] {
const nodes: AnyNodeDefinition[] = [
// Shelf is a new kind — no legacy code to flag against. It ships
// unconditionally so users can place it from the tool palette.
shelfDefinition as unknown as AnyNodeDefinition,
]
if (isSpawnRegistryEnabled()) {
nodes.push(spawnDefinition as unknown as AnyNodeDefinition)
}
return nodes
}
/**
* Built-in plugin bundling every node kind shipped with the Pascal editor.
*
* Apps load this once at bootstrap (`loadPlugin(builtinPlugin)`) before
* mounting the viewer. New built-in nodes are added by creating a folder
* here under `src/<kind>/` and appending its `NodeDefinition` to `getBuiltinNodes`.
* here under `src/<kind>/` and appending its `NodeDefinition` below.
*
* External plugins follow the exact same shape — same `Plugin` type, same
* `loadPlugin` call path. This is intentional: the API is stress-tested
* by built-ins before any third-party plugin lands.
*
* Phase 2 status: shelf is a brand-new kind. Spawn is migrated to the
* registry path — the legacy SpawnRenderer / SpawnTool files are still
* present in viewer/editor packages but short-circuited by the Phase 0
* dispatch shims (`nodeRegistry.has('spawn')` is true → legacy path
* yields). Legacy spawn files are deleted in a follow-up PR.
*/
export const builtinPlugin: Plugin = {
id: 'pascal:core',
apiVersion: 1,
nodes: getBuiltinNodes(),
nodes: [
shelfDefinition as unknown as AnyNodeDefinition,
spawnDefinition as unknown as AnyNodeDefinition,
],
}
export { shelfDefinition } from './shelf'
+1 -7
View File
@@ -5,13 +5,7 @@ import { useNodeEvents, useViewer } from '@pascal-app/viewer'
import { useMemo, useRef } from 'react'
import { Color, type Group, Shape } from 'three'
// TEMPORARY (Phase 2 verification): the registry-driven renderer paints
// spawns RED so you can visually tell which dispatch path is live. The
// legacy renderer in @pascal-app/viewer is still green. Revert this to
// '#22c55e' once the registry path is signed off for parity. Tracked by
// the NEXT_PUBLIC_USE_REGISTRY_FOR_SPAWN flag — if a spawn renders red
// you're on the new path; green = legacy.
const SPAWN_COLOR = new Color('#ef4444')
const SPAWN_COLOR = new Color('#22c55e')
/**
* Registry-driven spawn renderer. Behaviorally identical to the legacy