SelectionManager: route item to furnish phase before registry fallback

User report: items needed a double click to select after the item kind
registered (Phase 5). Root cause: `getSelectionTarget` checked
`isRegistrySelectable(node.type)` as part of the FIRST branch (which
routes to structure phase), matching `item` before the item-specific
branch below could route door/window-category items to structure +
everything else to furnish.

Effect: clicking an item triggered phase switch (structure ← furnish),
then the next click selected. Hence the double click.

Fix:
 1. Item-specific case moved to the TOP of getSelectionTarget. Its
    asset.category-driven routing (door/window items → structure;
    everything else → furnish) beats any generic registry fallback.
 2. Generic registry fallback at the bottom now reads `def.category`
    to pick the phase — `category: 'furnish'` → furnish phase,
    everything else → structure/elements. Future furnish-category
    kinds (only shelf right now) route correctly without a special
    case.
 3. `isRegistrySelectable(node.type)` clause removed from the
    structure branch — replaced by the def.category check at the
    bottom.

Net: single-click selection works for items again, and the routing
logic is now cleanly capability/category-driven instead of "all
registered kinds → structure" which was a Stage A simplification
that broke as soon as a furnish-category kind registered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-15 15:29:06 -04:00
co-authored by Claude Opus 4.7
parent 8d65be17fa
commit df07f7bcb2
@@ -11,6 +11,7 @@ import {
type ItemNode, type ItemNode,
isRegistrySelectable, isRegistrySelectable,
type NodeEvent, type NodeEvent,
nodeRegistry,
type RoofEvent, type RoofEvent,
type RoofNode, type RoofNode,
type RoofSegmentEvent, type RoofSegmentEvent,
@@ -691,6 +692,25 @@ const SELECTION_STRATEGIES: Record<string, SelectionStrategy> = {
} }
const getSelectionTarget = (node: AnyNode): SelectionTarget | null => { const getSelectionTarget = (node: AnyNode): SelectionTarget | null => {
// Item is checked FIRST so its asset.category-driven routing (door/
// window items land in structure phase, everything else in furnish)
// beats the generic registry fallback below. Without this, registering
// `item` (Phase 5) made isRegistrySelectable('item') match the
// structure branch first, breaking single-click selection: first click
// switched the editor to structure phase, second click selected.
if (node.type === 'item') {
const item = node as ItemNode
if (item.asset.category === 'door' || item.asset.category === 'window') {
return {
phase: 'structure',
structureLayer: 'elements',
}
}
return {
phase: 'furnish',
}
}
if (node.type === 'zone') { if (node.type === 'zone') {
return { return {
phase: 'structure', phase: 'structure',
@@ -711,10 +731,7 @@ const getSelectionTarget = (node: AnyNode): SelectionTarget | null => {
node.type === 'stair-segment' || node.type === 'stair-segment' ||
node.type === 'spawn' || node.type === 'spawn' ||
node.type === 'window' || node.type === 'window' ||
node.type === 'door' || node.type === 'door'
// Registry-driven kinds default to structure/elements (Phase 4 reads
// `definition.presentation.paletteSection` to route correctly).
isRegistrySelectable(node.type)
) { ) {
return { return {
phase: 'structure', phase: 'structure',
@@ -722,18 +739,16 @@ const getSelectionTarget = (node: AnyNode): SelectionTarget | null => {
} }
} }
if (node.type === 'item') { // Registry-driven kinds (Phase 5+): route by `def.category`. Built-ins
const item = node as ItemNode // above match before this fallback. Furnish-category kinds (shelf,
if (item.asset.category === 'door' || item.asset.category === 'window') { // item — already handled above) land on the furnish phase; structure-
return { // category kinds (everything else) on structure/elements.
phase: 'structure', const def = nodeRegistry.get(node.type)
structureLayer: 'elements', if (def) {
} if (def.category === 'furnish') {
} return { phase: 'furnish' }
return {
phase: 'furnish',
} }
return { phase: 'structure', structureLayer: 'elements' }
} }
return null return null