Phase 5 batch kind: slab migrates to registry (always-on)

Same shape as fence — thin renderer + system re-export, capabilities
declared, panel slider-drag fix recipe applied. Pure geometry +
floor-plan ports are later milestones.

Files added (packages/nodes/src/slab/):
 - schema.ts: re-exports SlabNode from core.
 - parametrics.ts: elevation slider only. Polygon + holes edited
   via floor-plan boundary / hole editors, not number inputs.
 - definition.ts: capabilities (no `movable` — slab move is bespoke
   whole-translation through MoveSlabTool that integrates with the
   boundary editor; capability-driven dispatch keeps the legacy mover),
   surfaces.top with elevation-as-height for stacked items, relations
   (hosts: ['item'], cascadeDelete: 'descendants'), toolHints (trace /
   finish / cancel for the placement tool).
 - renderer.tsx: thin placeholder mesh + markDirty on mount + node
   events + cached material via the same getSlabMaterial pattern as
   the legacy renderer (preset apply on shared material instance).
 - system.tsx: re-exports the legacy SlabSystem from viewer.
 - index.ts: barrel.

Files changed:
 - packages/viewer/src/index.ts: exports SlabSystem (already had
   DEFAULT_SLAB_MATERIAL, applyMaterialPresetToMaterials, createMaterial
   from earlier exports).
 - packages/nodes/src/index.ts: appends slabDefinition unconditionally
   to builtinPlugin.nodes.
 - packages/editor/src/components/ui/panels/slab-panel.tsx: applied
   the panel slider-drag fix recipe from plans/editor-node-registry.md
   prophylactically (nodeRef pattern, useScene.getState().updateNode
   inside handler, drop subscribed updateNode dep). Slab's elevation
   slider is the only drag-driven control in the panel — would have
   triggered the same Maximum update depth cascade as wall/fence.

No behavior change. Slab now mounts via the registry path, but the
legacy SlabSystem still does the actual polygon triangulation + hole
CSG work (re-exported, not duplicated).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-15 14:12:45 -04:00
co-authored by Claude Opus 4.7
parent fc9a5d02a0
commit 4891f681f3
9 changed files with 260 additions and 4 deletions
+88
View File
@@ -0,0 +1,88 @@
import type { NodeDefinition } from '@pascal-app/core'
import { slabParametrics } from './parametrics'
import { SlabNode } from './schema'
/**
* Slab — Phase 5 batch kind, polygon-based.
*
* Capabilities:
* - **No `movable`**: slab's "move" today is whole-slab translation via
* legacy `MoveSlabTool`, which integrates with the floor-plan boundary /
* hole editors. Per the capability-driven dispatch rule, omitting
* `movable` keeps the legacy mover (preserves polygon-aware behavior).
* Migration to the generic mover is possible in a later milestone if
* the legacy mover proves equivalent.
* - **`surfaces.top`**: items host on the slab top at `elevation`.
* - `selectable`, `duplicable`, `deletable` standard.
*
* Relations:
* - `hosts: ['item']` — items mount on the slab top.
* - `cascadeDelete: 'descendants'` — deleting a slab removes hosted items.
*
* Renderer + system: thin renderer + re-export of the legacy `SlabSystem`.
* Same shape as wall / fence runtime port.
*
* Tool field absent: slab has 3 tools (slab-tool, boundary-editor, hole-
* editor) wired through editor state, not registry dispatch.
*/
export const slabDefinition: NodeDefinition<typeof SlabNode> = {
kind: 'slab',
schemaVersion: 1,
schema: SlabNode,
category: 'structure',
defaults: () => ({
object: 'node',
parentId: null,
visible: true,
metadata: {},
polygon: [],
holes: [],
holeMetadata: [],
elevation: 0.05,
autoFromWalls: false,
}),
capabilities: {
selectable: { hitVolume: 'bbox' },
surfaces: {
top: { height: (n) => (n as SlabNode).elevation },
},
duplicable: true,
deletable: true,
},
relations: {
hosts: ['item'],
cascadeDelete: 'descendants',
},
parametrics: slabParametrics,
renderer: {
kind: 'parametric',
module: () => import('./renderer'),
},
system: {
module: () => import('./system'),
priority: 4,
},
toolHints: [
{ key: 'Left click', label: 'Trace slab outline' },
{ key: 'Enter', label: 'Finish slab' },
{ key: 'Esc', label: 'Cancel' },
],
presentation: {
label: 'Slab',
description: 'A polygon-bounded floor surface that hosts items on top.',
icon: { kind: 'iconify', name: 'lucide:square' },
paletteSection: 'structure',
paletteOrder: 30,
},
mcp: {
description: 'A polygon-bounded slab (floor) with optional cutout holes.',
},
}