Symptom: a shelf placed on a level with a raised slab underneath
visually clipped through it — `ItemSystem` lifted items onto slabs,
but shelves (and other floor-placed registry kinds) had no equivalent
path.
Fix: lift the slab-elevation logic out of `ItemSystem` into a generic
`<FloorElevationSystem>` keyed off a new `capabilities.floorPlaced`
config. Any kind that opts in declares a `footprint(node)` (dimensions
+ rotation used to query overlapping slabs) and an optional `applies`
predicate (skips items whose `asset.attachTo` is wall / ceiling).
The new system runs at frame priority 1 so its `mesh.position.y`
override lands before `ItemSystem` / `GeometrySystem` (priority 2)
clear the dirty mark. The spatial-grid sync's `markNodesOverlappingSlab`
also dropped its hardcoded `item` branch in favour of an iteration over
every registered kind that declares `floorPlaced` — so any new
floor-placed kind picks up slab-driven re-elevation automatically.
Tagged kinds:
- `item` — `footprint = getScaledDimensions`, `applies = !asset.attachTo`
- `shelf` — `footprint = (w, h, d)`
- `column` — `footprint = (w, h, d)`
- `spawn` — `footprint = (0.6, 1.8, 0.6)` (marker)
`ItemSystem` retains only the wall-side z-offset block (`mesh.position.z =
wallThickness / 2`). The elevation block + its `getScaledDimensions` /
`resolveLevelId` / `spatialGridManager` imports moved to the generic
system.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wholesale move of every remaining kind into its own subdirectory under
`packages/nodes/src/`, finishing the registry-driven migration. Each
kind now ships its definition, schema (re-exported from core), and any
of `geometry` / `renderer` / `system` / `floorplan` / `tool` /
`move-tool` / `panel` / `floorplan-move` / `floorplan-affordances` /
`parametrics` / `preview` it needs — no per-kind code remains under
`packages/editor/src/components/tools/` or
`packages/viewer/src/components/renderers/`.
Deleted (replaced by registry-driven equivalents):
- `tools/{ceiling,column,door,fence,item,slab,spawn,wall,window}/...`
(boundary editors, hole editors, placement tools, move tools,
endpoint movers, curve tools, helpers, math libs)
- `ui/helpers/{ceiling,slab,wall}-helper.tsx`
- `ui/panels/{column,door,elevator,item,roof,roof-segment,spawn,
stair,stair-segment,wall,window}-panel.tsx`
- `viewer/src/components/renderers/{building,ceiling,column,door,
elevator,fence,guide,item,level,roof,roof-segment,scan,site,slab,
spawn,stair,stair-segment,wall,window,zone}-renderer.tsx`
- `viewer/src/components/viewer/legacy-system.tsx`
Added under `packages/nodes/src/`:
- `building/`, `column/`, `elevator/`, `guide/`, `level/`, `roof/`,
`roof-segment/`, `scan/`, `shared/`, `site/`, `stair/`,
`stair-segment/` packages with definition + schema + renderer / system
/ floorplan / panel as appropriate.
- New `floorplan-move.ts` for every kind that supports 2D moves
(ceiling, door, item, shelf, slab, window) — single registry-driven
dispatch path via `def.floorplanMoveTarget`.
- New `floorplan-affordances.ts` for kinds with polygon / endpoint
drags (ceiling, fence, slab, wall) — using the shared
`polygon-vertex-affordance` factories.
- New per-kind `panel.tsx` for kinds with custom inspector content
(door, item, shelf, spawn, wall, window).
- New per-kind `tool.tsx` for placement (door, item, shelf, window).
- New per-kind `move-tool.tsx` for kinds with custom 3D move flows
(door, item, slab, window).
Coordinator + manager updates in `packages/editor/`:
- `tool-manager.tsx` resolves tools from the registry only — no
hardcoded type→component map.
- `panel-manager.tsx` resolves inspector panels the same way.
- `placement-{coordinator,strategies,types}.ts` extended with
shelf-surface placement.
- `selection-manager.tsx` adds the registry-selectable fallback.
- `floorplan-panel.tsx`, `floorplan-background-placement.ts`,
`floorplan-render-context.tsx` updated for the registry layer's new
contract (props, affordance dispatch, render context).
Viewer updates:
- `viewer/index.tsx` drops legacy renderer mounts.
- `node-renderer.tsx` resolves by registry only.
- `scene-bvh.tsx`, `use-node-events.ts`, `level-system.tsx`,
`wall-cutout.tsx`, `zone-system.tsx`, `materials.ts` adjusted for
the registry-only world.
Sidebar tree nodes for ceiling / fence / slab / shelf / tree-node
updated to read from the registered nodes instead of the deleted
legacy renderer trees.
Wiki: new `plugin-authoring.md` page, README index updated.
Tests in `packages/nodes/src/index.test.ts` validate every registered
kind has the required shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
byType was a hardcoded object keyed by the built-in node kinds. With
the registry, kinds can come from @pascal-app/nodes (or future
plugins) — so byType now wraps a Map via a Proxy that auto-creates an
empty Set the first time any kind is touched.
Built-in kinds are still pre-seeded at module init so the fast path
(no Proxy trap) is preserved. clear() iterates the backing Map.
useRegistry's `type` parameter widens from `keyof typeof byType` to
`KnownNodeKind | (string & {})` — preserves autocomplete for
built-ins while accepting plugin-supplied kinds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace runtime mesh-based bounding-box computation with static dimension-based polygons for item footprints
- Add snapUpToGridStep() and getGridAlignedDimensions() to placement-math for grid-cell-aligned placement wireframes
- Add expandBoundsToGrid() to use-placement-coordinator for consistent wireframe snapping
- Add currentCursorRotationY to PlacementContext; preserve world orientation across item-surface transitions
- Fix item detach from surface: use worldToBuildingLocal() instead of event.localPosition to avoid coordinate-space jump
- Subscribe to useLiveTransforms in FloorplanPanel during placement so R/T keyboard rotation refreshes the 2D overlay immediately
- Fix FloorplanItemImage rotation (+180° to account for top-down camera capture orientation)
- Simplify spatial-grid-manager: single dimension-based getItemLocalBounds(), removes runtime mesh-metadata path
- Remove item-mesh-metadata system (compute-item-mesh-metadata, item-mesh-metadata-system, sync-request)
* feat: railing on the straight stairs and new fence
* feat: added spiral and curved stairs with bug fix for fence
* feat:fence are linked to each other ... so moving one move the other sharing the same coordinate
* fix: update stair railing logic to include front-side attachments for terminal landings
* Integrate fence rendering into the fence system
* fix: pass nodeId instead of undefined node to WallTreeNode and FenceTreeNode
TreeNode was passing `node` (undefined variable) instead of `nodeId` to
WallTreeNode and FenceTreeNode, causing a runtime ReferenceError.
Updated FenceTreeNode to accept nodeId and look up the node from the
scene store internally, consistent with all other tree node components.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: update fence icon with new isometric design
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Aymeric Rabot <aymeric@pascal.app>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Mark stair nodes as dirty when slabs change so stairs properly stack on
top of floor slabs. StairSystem now computes slab-driven elevation by
sampling segment centers against the spatial grid.
Changes:
- spatial-grid-sync: mark stair nodes dirty on slab changes (same level)
- stair-system: syncStairGroupElevation + getStairSlabElevation compute
Y offset from slab data; rotateXZ helper for world-space transforms
- stair-renderer: use position-x/position-z so the system controls Y
The spatial-grid manager, scene-store subscriptions, and the viewer
outliner's Object3D arrays are module-level singletons that previously
lived for the whole page lifetime. When an app unmounted the Editor
(e.g. navigating away from the 3D editor and back in an SPA host) they
retained references to the disposed Three.js scene graph, which caused
the app to freeze on re-entry as the stale data was replayed against a
fresh scene.
- initSpatialGridSync now returns the unsubscribe handle from its
scene-store subscription so callers can detach it.
- initializeEditorRuntime becomes reversible: it wires up the spatial
grid, space detection and SFX bus on mount and returns a teardown
closure that detaches both store subscriptions, clears the spatial
grid manager, and truncates the viewer outliner's selected/hovered
arrays in place.
- The Editor component's runtime-init effect now returns that teardown,
so the singletons are reset cleanly every time the Editor unmounts.
- initSFXBus is now idempotent via an internal guard so repeated
Editor mounts do not stack duplicate mitt listeners.
Co-authored-by: Claude <noreply@anthropic.com>
Stair system (full stack):
- New StairNode + StairSegmentNode schemas with flights, landings, L/U-shapes
- StairSystem: geometry generation with throttled per-frame updates
- Stair tool, edit system, panels, tree node, and renderers
- Event bus types, scene registry, and command palette entries
Scene graph utilities:
- cloneLevelSubtree: deep-clone a level with remapped IDs
- forkSceneGraph: clone + strip scan/guide nodes for project forking
Core improvements:
- Read-only mode on scene store (blocks create/update/delete when locked)
- readOnly guards on node-actions and collection actions
- Upload store for scan/guide file upload handling
Viewer state:
- previewSelectedIds for box-select live preview
- hoverHighlightMode (default/delete) for delete-mode hover outline
Major changes:
- Roof system rewrite with roof-segment support
- Scene store refactor
- Spatial grid improvements
- Item light system
- Post-processing and selection manager updates
- Perf monitor component
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>