feat: stair system, scene graph utilities, read-only mode, viewer state improvements (#210)

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
This commit is contained in:
Pascal
2026-04-05 01:20:43 -04:00
committed by GitHub
parent 08f93f527a
commit 0bd0cab533
34 changed files with 1914 additions and 7 deletions
+10 -1
View File
@@ -20,6 +20,10 @@ type Outliner = {
type ViewerState = {
selection: SelectionPath
previewSelectedIds: BaseNode['id'][]
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void
hoverHighlightMode: 'default' | 'delete'
setHoverHighlightMode: (mode: 'default' | 'delete') => void
hoveredId: AnyNode['id'] | ZoneNode['id'] | null
setHoveredId: (id: AnyNode['id'] | ZoneNode['id'] | null) => void
@@ -75,6 +79,10 @@ const useViewer = create<ViewerState>()(
persist(
(set) => ({
selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
previewSelectedIds: [],
setPreviewSelectedIds: (ids) => set({ previewSelectedIds: ids }),
hoverHighlightMode: 'default',
setHoverHighlightMode: (mode) => set({ hoverHighlightMode: mode }),
hoveredId: null,
setHoveredId: (id) => set({ hoveredId: id }),
@@ -164,7 +172,7 @@ const useViewer = create<ViewerState>()(
if (updates.selectedIds === undefined) newSelection.selectedIds = []
}
return { selection: newSelection }
return { selection: newSelection, previewSelectedIds: [] }
}),
resetSelection: () =>
@@ -175,6 +183,7 @@ const useViewer = create<ViewerState>()(
zoneId: null,
selectedIds: [],
},
previewSelectedIds: [],
}),
outliner: { selectedObjects: [], hoveredObjects: [] },