Files
editor/packages/nodes/src/shelf/definition.ts
T
Wassim SAMADandClaude Opus 4.7 d10d7f8deb Phase 4 follow-on: floor-plan interaction + def.toolHints + spawn floorplan builder
Three additions on top of the floor-plan registry contract:

1. def.toolHints + RegisteredToolHelper (registry contract for the
   shortcut hint panel)

   - New ToolHint type in core: { key, label } static array.
   - Added `def.toolHints?: ToolHint[]` to NodeDefinition.
   - New <RegisteredToolHelper hints={...}> in editor — same visual
     styling as WallHelper / ItemHelper but data-driven.
   - HelperManager: registry-first check before falling through to the
     hand-written per-tool switch. Per-tool helper files get deleted
     as their kind migrates `toolHints` in.
   - Shelf + spawn definitions ship toolHints today; wall ports in
     Phase 3 Milestone C alongside its tool/affordance port.

2. Floor-plan interaction layer (selection + drag-to-move)

   - <FloorplanRegistryLayer> now wraps each entry in an interactive
     <g>:
       * Click → useViewer.setSelection({ selectedIds: [id] }).
         Selection visual is a thicker accent-colored stroke applied
         via withSelectionStyle() recursion through the FloorplanGeometry
         tree — kinds don't author selection decoration.
       * Drag → imperative SVG transform during the gesture, single
         updateNode commit on pointerup. Same "smooth move" pattern as
         MoveRegistryNodeTool for 3D drag: no per-tick store update,
         no React re-render storm, no zundo bloat. Coordinate
         conversion via svg.getScreenCTM().inverse().
       * useScene.temporal.pause/resume brackets the gesture so one
         drag = one undo step.
   - Global pointermove / pointerup listeners so the gesture survives
     the cursor leaving the entry's bounding box (matches the legacy
     elevator-resize-drag and item-drag patterns in floorplan-panel).

3. Spawn floor-plan builder (deferred wiring)

   - buildSpawnFloorplan written but NOT wired on the definition —
     spawn already renders in the legacy floorplan-panel.tsx via
     `floorplanSpawnEntries`, and wiring def.floorplan now would
     double-render. The pure builder lives in nodes/src/spawn/
     floorplan.ts ready to wire when the legacy inline branch is
     removed (Phase 5 spawn-floorplan migration PR — same shape as
     wall's feature flag, but per kind inside the legacy panel).

Plan updated: floor-plan interaction section locks the click/drag
contract in, wall-floor-plan-as-legacy note flags everything advanced
the user sees today as legacy that ports alongside Milestone C.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 10:51:22 -04:00

77 lines
2.3 KiB
TypeScript

import type { NodeDefinition } from '@pascal-app/core'
import { buildShelfFloorplan } from './floorplan'
import { buildShelfGeometry } from './geometry'
import { shelfParametrics } from './parametrics'
import { ShelfNode } from './schema'
export const shelfDefinition: NodeDefinition<typeof ShelfNode> = {
kind: 'shelf',
schemaVersion: 1,
schema: ShelfNode,
category: 'furnish',
defaults: () => ({
object: 'node',
parentId: null,
visible: true,
metadata: {},
position: [0, 0, 0],
rotation: [0, 0, 0],
width: 1.2,
depth: 0.3,
thickness: 0.04,
height: 0.9,
bracketStyle: 'minimal',
color: '#a07050',
}),
capabilities: {
movable: { axes: ['x', 'z'], gridSnap: true },
rotatable: {
axes: ['y'],
snapAngles: [0, Math.PI / 4, Math.PI / 2, (3 * Math.PI) / 4, Math.PI],
},
// The whole point of shelf: things can stack on it. Surface height
// resolves from the node so multiple shelves at different heights stack
// correctly (vs a fixed-height table).
surfaces: {
top: { height: (n) => (n as ShelfNode).height + (n as ShelfNode).thickness },
},
selectable: { hitVolume: 'bbox' },
duplicable: true,
deletable: true,
},
parametrics: shelfParametrics,
// Three-checkbox composition: shelf needs only pure builder functions.
// The framework's <ParametricNodeRenderer> + <GeometrySystem> handle 3D
// mount and rebuild on dirty; the <FloorplanRegistryLayer> calls
// buildShelfFloorplan for the 2D top-down view. No renderer.tsx, no
// system.tsx, no inline floor-plan SVG — see
// `wiki/architecture/node-definitions.md`. Shelf is the reference port
// proving Phase 4's boilerplate collapse for both 3D and 2D.
geometry: buildShelfGeometry,
floorplan: buildShelfFloorplan,
preview: () => import('./preview'),
tool: () => import('./tool'),
toolHints: [
{ key: 'Left click', label: 'Place shelf' },
{ key: 'Esc', label: 'Cancel' },
],
presentation: {
label: 'Shelf',
description: 'A horizontal surface for stacking other items.',
icon: { kind: 'iconify', name: 'lucide:layers' },
paletteSection: 'structure',
paletteOrder: 50,
},
mcp: {
description:
'A parametric shelf with adjustable dimensions and bracket style. Stackable on its top surface.',
},
}