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:
co-authored by
Claude Opus 4.7
parent
857ddd4d95
commit
6d97a87547
@@ -6,6 +6,7 @@ import {
|
||||
type BuildingNode,
|
||||
type ColumnNode,
|
||||
emitter,
|
||||
getSelectableKinds,
|
||||
type ItemNode,
|
||||
type LevelNode,
|
||||
type NodeEvent,
|
||||
@@ -25,6 +26,10 @@ const tempWorldPos = new Vector3()
|
||||
// Tolerance for edge detection (in meters)
|
||||
const EDGE_TOLERANCE = 0.5
|
||||
|
||||
// Hardcoded kinds the viewer's selection manager knows about. Registry kinds
|
||||
// (any NodeDefinition with `capabilities.selectable`) are merged in at
|
||||
// runtime via getSelectableKinds() — Phase 6 collapses this into a single
|
||||
// registry-driven list.
|
||||
type SelectableNodeType =
|
||||
| 'building'
|
||||
| 'level'
|
||||
@@ -35,11 +40,11 @@ type SelectableNodeType =
|
||||
| 'door'
|
||||
| 'column'
|
||||
| 'item'
|
||||
| 'shelf'
|
||||
| 'slab'
|
||||
| 'ceiling'
|
||||
| 'roof'
|
||||
| 'roof-segment'
|
||||
| (string & {})
|
||||
|
||||
// Expand polygon outward by a small amount to include items on edges
|
||||
const expandPolygon = (polygon: [number, number][], tolerance: number): [number, number][] => {
|
||||
@@ -330,7 +335,9 @@ export const SelectionManager = () => {
|
||||
useViewer.setState({ hoveredId: null })
|
||||
}
|
||||
|
||||
// Subscribe to all node types
|
||||
// Subscribe to all node types. Hardcoded kinds + registry-supplied kinds
|
||||
// (any NodeDefinition declaring `capabilities.selectable`). Phase 6
|
||||
// collapses these into a single registry-driven list.
|
||||
const allTypes: SelectableNodeType[] = [
|
||||
'building',
|
||||
'level',
|
||||
@@ -339,7 +346,6 @@ export const SelectionManager = () => {
|
||||
'fence',
|
||||
'item',
|
||||
'column',
|
||||
'shelf',
|
||||
'slab',
|
||||
'ceiling',
|
||||
'roof',
|
||||
@@ -347,17 +353,22 @@ export const SelectionManager = () => {
|
||||
'window',
|
||||
'door',
|
||||
]
|
||||
for (const type of allTypes) {
|
||||
emitter.on(`${type}:enter`, onEnter)
|
||||
emitter.on(`${type}:leave`, onLeave)
|
||||
emitter.on(`${type}:click`, onClick)
|
||||
const registryKinds = getSelectableKinds().filter(
|
||||
(k) => !(allTypes as readonly string[]).includes(k),
|
||||
) as SelectableNodeType[]
|
||||
const subscribedKinds = [...allTypes, ...registryKinds]
|
||||
|
||||
for (const type of subscribedKinds) {
|
||||
emitter.on(`${type}:enter` as any, onEnter as any)
|
||||
emitter.on(`${type}:leave` as any, onLeave as any)
|
||||
emitter.on(`${type}:click` as any, onClick as any)
|
||||
}
|
||||
|
||||
return () => {
|
||||
for (const type of allTypes) {
|
||||
emitter.off(`${type}:enter`, onEnter)
|
||||
emitter.off(`${type}:leave`, onLeave)
|
||||
emitter.off(`${type}:click`, onClick)
|
||||
for (const type of subscribedKinds) {
|
||||
emitter.off(`${type}:enter` as any, onEnter as any)
|
||||
emitter.off(`${type}:leave` as any, onLeave as any)
|
||||
emitter.off(`${type}:click` as any, onClick as any)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
Reference in New Issue
Block a user