Files
d36e8fcc78 fix: zone grid snapping, shelf position panel, deselect on item placement (#394)
* fix: zone grid snapping, shelf position panel, deselect on item placement

- Zone tool reads the editor's gridSnapStep (0.5/0.25/0.1/0.05) instead
  of a hardcoded 0.5 for both cursor move and click snapping.
- Shelf inspector gains a Position group (vec3 X/Y/Z sliders), matching
  the item panel.
- Item catalog clears the viewer selection before arming placement so
  shortcuts (rotate & co) don't hit both the ghost and the selected node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: move level display naming into @pascal-app/core

getDefaultLevelName / getLevelDisplayName ("Ground Floor" / "Floor N" /
"Basement N") lived in packages/editor's internal lib, so viewer-only
surfaces couldn't reach them and fell back to hand-rolled "Level N"
labels. The helpers are pure domain logic, so they move to core and
export from its barrel; the editor package's seven call sites now import
them from there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:56:24 -04:00

85 lines
2.9 KiB
TypeScript

import type { ParametricDescriptor } from '@pascal-app/core'
import type { ShelfNode } from './schema'
/**
* Inspector descriptor for the parametric shelf. Drives both the
* auto-derived inspector UI and the AI/MCP `create_shelf` /
* `update_shelf` tools with bounded JSON-schema parameters.
*
* Fields are grouped by intent: Style first (what kind of shelf), then
* Topology (rows / columns / back / sides / bottom + wall-shelf bracket
* style), then Dimensions. Surface material is paint-tray driven (same
* flow as walls / slabs / stairs) and intentionally not surfaced here.
*/
export const shelfParametrics: ParametricDescriptor<ShelfNode> = {
groups: [
{
label: 'Style',
fields: [
{
key: 'style',
kind: 'enum',
options: ['wall-shelf', 'bookshelf', 'open-rack', 'cubby'],
},
],
},
{
label: 'Topology',
fields: [
{ key: 'rows', kind: 'number', min: 1, max: 8, step: 1 },
// Columns only meaningful for kinds with vertical dividers.
{
key: 'columns',
kind: 'number',
min: 1,
max: 6,
step: 1,
visibleIf: (n) => n.style === 'bookshelf' || n.style === 'cubby',
},
// Sides toggle only applies to bookshelf (cubby always on, the
// others use their own post structure).
{
key: 'withSides',
kind: 'boolean',
visibleIf: (n) => n.style === 'bookshelf',
},
// Back toggle only applies to bookshelf and open-rack (cubby
// always has a back, wall-shelf has no back).
{
key: 'withBack',
kind: 'boolean',
visibleIf: (n) => n.style === 'bookshelf' || n.style === 'open-rack',
},
// Bottom toggle only applies to bookshelf and cubby — closes
// the lowest cell with a floor board so items can host there.
{
key: 'withBottom',
kind: 'boolean',
visibleIf: (n) => n.style === 'bookshelf' || n.style === 'cubby',
},
// Bracket style only matters for wall-shelf — the other styles
// structure themselves through sides / posts / dividers.
{
key: 'bracketStyle',
kind: 'enum',
options: ['minimal', 'industrial', 'hidden'],
visibleIf: (n) => n.style === 'wall-shelf',
},
],
},
{
label: 'Dimensions',
fields: [
{ key: 'width', kind: 'number', unit: 'm', min: 0.3, max: 3.0, step: 0.05 },
{ key: 'depth', kind: 'number', unit: 'm', min: 0.1, max: 1.0, step: 0.05 },
{ key: 'thickness', kind: 'number', unit: 'm', min: 0.01, max: 0.1, step: 0.005 },
{ key: 'height', kind: 'number', unit: 'm', min: 0.05, max: 2.5, step: 0.05 },
],
},
{
label: 'Position',
fields: [{ key: 'position', kind: 'vec3' }],
},
],
}