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>
79 lines
2.5 KiB
TypeScript
79 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
import { type SpawnNode, useLiveTransforms, useRegistry } from '@pascal-app/core'
|
|
import { useNodeEvents, useViewer } from '@pascal-app/viewer'
|
|
import { useMemo, useRef } from 'react'
|
|
import { Color, type Group, Shape } from 'three'
|
|
|
|
const SPAWN_COLOR = new Color('#22c55e')
|
|
|
|
/**
|
|
* Registry-driven spawn renderer. Behaviorally identical to the legacy
|
|
* `@pascal-app/viewer/components/renderers/spawn/spawn-renderer.tsx` — same
|
|
* geometry, same colors, same event surface. When the spawn definition lands
|
|
* in `builtinPlugin.nodes`, the Phase 0 dispatch shims switch the renderer
|
|
* here and the legacy one is short-circuited.
|
|
*
|
|
* Lives in `@pascal-app/nodes` (not viewer) so the kind owns its own render
|
|
* code. Phase 5's batch migration applies the same pattern to every node.
|
|
*/
|
|
const SpawnRenderer = ({ node }: { node: SpawnNode }) => {
|
|
const ref = useRef<Group>(null!)
|
|
const handlers = useNodeEvents(node, 'spawn')
|
|
const liveTransform = useLiveTransforms((state) => state.get(node.id))
|
|
const walkthroughMode = useViewer((state) => state.walkthroughMode)
|
|
|
|
useRegistry(node.id, 'spawn', ref)
|
|
|
|
const materialProps = useMemo(
|
|
() => ({
|
|
color: SPAWN_COLOR,
|
|
emissive: SPAWN_COLOR,
|
|
emissiveIntensity: 0.08,
|
|
metalness: 0.03,
|
|
roughness: 0.42,
|
|
}),
|
|
[],
|
|
)
|
|
|
|
const arrowShape = useMemo(() => {
|
|
const shape = new Shape()
|
|
shape.moveTo(0, 0.24)
|
|
shape.lineTo(-0.18, -0.14)
|
|
shape.lineTo(0.18, -0.14)
|
|
shape.closePath()
|
|
return shape
|
|
}, [])
|
|
|
|
return (
|
|
<group
|
|
position={liveTransform?.position ?? node.position}
|
|
ref={ref}
|
|
rotation={[0, liveTransform?.rotation ?? node.rotation, 0]}
|
|
visible={!walkthroughMode}
|
|
>
|
|
<mesh position={[0, 0.09, 0]} rotation={[-Math.PI / 2, 0, 0]} {...handlers}>
|
|
<ringGeometry args={[0.34, 0.48, 48]} />
|
|
<meshStandardMaterial {...materialProps} />
|
|
</mesh>
|
|
|
|
<mesh position={[0, 0.1, -0.52]} rotation={[-Math.PI / 2, 0, 0]} {...handlers}>
|
|
<shapeGeometry args={[arrowShape]} />
|
|
<meshStandardMaterial {...materialProps} />
|
|
</mesh>
|
|
|
|
<mesh position={[0, 0.41, 0]} {...handlers}>
|
|
<boxGeometry args={[0.3, 0.54, 0.16]} />
|
|
<meshStandardMaterial {...materialProps} />
|
|
</mesh>
|
|
|
|
<mesh position={[0, 0.83, 0]} {...handlers}>
|
|
<boxGeometry args={[0.18, 0.18, 0.18]} />
|
|
<meshStandardMaterial {...materialProps} />
|
|
</mesh>
|
|
</group>
|
|
)
|
|
}
|
|
|
|
export default SpawnRenderer
|