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

Structurally identical to slab. Stage A migration: registers the kind,
wraps the legacy renderer + system, applies the panel slider-drag fix
recipe.

Files added (packages/nodes/src/ceiling/):
 - schema.ts: re-exports CeilingNode from core.
 - parametrics.ts: height slider only. Polygon + holes via floor-plan
   editors.
 - definition.ts: capabilities (no `movable`, `surfaces.top` mapped to
   `height`), relations (hosts: ['item'] for ceiling-mounted lights /
   fans, cascadeDelete: 'descendants'), toolHints, parametrics.
 - renderer.tsx: wrap-export of legacy CeilingRenderer. The legacy
   renderer uses TSL shader code for grid-line patterns (~100 lines);
   not worth duplicating at Stage A. Per-stage migration plan in
   plans/editor-node-registry.md moves the renderer body into this
   folder at Stage B/F.
 - system.tsx: re-exports legacy CeilingSystem.
 - index.ts: barrel.

Files changed:
 - packages/viewer/src/index.ts: new public exports for CeilingRenderer
   + CeilingSystem.
 - packages/nodes/src/index.ts: appends ceilingDefinition.
 - packages/editor/src/components/ui/panels/ceiling-panel.tsx: panel
   slider-drag fix recipe applied (nodeRef pattern, useScene.getState()
   inside handler, drop subscribed updateNode dep) so the height
   slider doesn't trigger the same cascade fence + wall + slab fixed.

Phase 5 progress: shelf , spawn , wall , fence , slab , ceiling .
Six kinds on the registry. Door / window / item / stair / roof / zone
follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-05-15 14:32:01 -04:00
co-authored by Claude Opus 4.7
parent 4891f681f3
commit 2dd50fa5be
9 changed files with 151 additions and 4 deletions
@@ -3,7 +3,7 @@
import { type AnyNode, type CeilingNode, useScene } from '@pascal-app/core'
import { useViewer } from '@pascal-app/viewer'
import { Edit, Move, Plus, Trash2 } from 'lucide-react'
import { useCallback, useEffect } from 'react'
import { useCallback, useEffect, useRef } from 'react'
import { sfxEmitter } from '../../../lib/sfx-bus'
import useEditor from '../../../store/use-editor'
import { ActionButton, ActionGroup } from '../controls/action-button'
@@ -14,7 +14,6 @@ import { PanelWrapper } from './panel-wrapper'
export function CeilingPanel() {
const selectedId = useViewer((s) => s.selection.selectedIds[0])
const setSelection = useViewer((s) => s.setSelection)
const updateNode = useScene((s) => s.updateNode)
const editingHole = useEditor((s) => s.editingHole)
const setEditingHole = useEditor((s) => s.setEditingHole)
const setMovingNode = useEditor((s) => s.setMovingNode)
@@ -23,12 +22,17 @@ export function CeilingPanel() {
selectedId ? (s.nodes[selectedId as AnyNode['id']] as CeilingNode | undefined) : undefined,
)
// Panel slider-drag fix recipe (plans/editor-node-registry.md): stable
// handler refs so slider drags don't trigger Maximum update depth.
const nodeRef = useRef(node)
nodeRef.current = node
const handleUpdate = useCallback(
(updates: Partial<CeilingNode>) => {
if (!selectedId) return
updateNode(selectedId as AnyNode['id'], updates)
useScene.getState().updateNode(selectedId as AnyNode['id'], updates)
},
[selectedId, updateNode],
[selectedId],
)
const handleClose = useCallback(() => {