@pascal-app/editor 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. package/README.md +106 -0
  2. package/package.json +42 -38
  3. package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
  4. package/src/components/editor/action-menu-button.tsx +83 -0
  5. package/src/components/editor/bake-thumbnail.tsx +307 -5
  6. package/src/components/editor/capture-level-picker.tsx +72 -0
  7. package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
  8. package/src/components/editor/construction-reveal-audio.tsx +40 -0
  9. package/src/components/editor/custom-camera-controls.tsx +275 -57
  10. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  11. package/src/components/editor/duplicate-node.ts +244 -0
  12. package/src/components/editor/editor-layout-mobile.tsx +37 -26
  13. package/src/components/editor/editor-layout-v2.tsx +132 -59
  14. package/src/components/editor/export-manager.tsx +47 -26
  15. package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
  16. package/src/components/editor/first-person/build-collider-world.ts +28 -7
  17. package/src/components/editor/first-person/ground-holes.test.ts +128 -0
  18. package/src/components/editor/first-person/ground-holes.ts +130 -0
  19. package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
  20. package/src/components/editor/first-person/mechanism-targets.ts +88 -0
  21. package/src/components/editor/first-person-controls.tsx +220 -9
  22. package/src/components/editor/floating-action-menu.tsx +63 -207
  23. package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
  24. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  25. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  26. package/src/components/editor/floor-region-controls.tsx +846 -0
  27. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  28. package/src/components/editor/floor-surface.test.ts +165 -0
  29. package/src/components/editor/floor-surface.ts +182 -0
  30. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  31. package/src/components/editor/floorplan-background-selection.ts +1 -3
  32. package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
  33. package/src/components/editor/floorplan-panel.tsx +414 -363
  34. package/src/components/editor/follow-pascal.test.tsx +912 -0
  35. package/src/components/editor/follow-pascal.tsx +354 -0
  36. package/src/components/editor/footprint-height-handle.tsx +204 -0
  37. package/src/components/editor/group-actions.ts +56 -2
  38. package/src/components/editor/group-move-3d.ts +2 -0
  39. package/src/components/editor/group-rotate-handle.tsx +2 -0
  40. package/src/components/editor/group-transform-shared.test.ts +460 -0
  41. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  42. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  43. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  45. package/src/components/editor/index.tsx +409 -197
  46. package/src/components/editor/join-walls-pill.tsx +44 -0
  47. package/src/components/editor/node-action-menu.tsx +68 -70
  48. package/src/components/editor/node-arrow-handles.tsx +25 -6
  49. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  50. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  52. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  53. package/src/components/editor/registry-action-contributions.tsx +80 -1
  54. package/src/components/editor/riser-diagram-panel.tsx +14 -7
  55. package/src/components/editor/room-controls.tsx +847 -0
  56. package/src/components/editor/room-floor-highlight.tsx +128 -0
  57. package/src/components/editor/room-handles-2d.tsx +354 -0
  58. package/src/components/editor/room-handles.tsx +675 -0
  59. package/src/components/editor/room-highlight.tsx +233 -0
  60. package/src/components/editor/room-labels.test.ts +850 -0
  61. package/src/components/editor/room-labels.tsx +1890 -0
  62. package/src/components/editor/room-transform-ghost.tsx +256 -0
  63. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  64. package/src/components/editor/selection-manager.tsx +474 -419
  65. package/src/components/editor/slab-hole-highlights.tsx +5 -2
  66. package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
  67. package/src/components/editor/snapshot-capture.test.ts +119 -60
  68. package/src/components/editor/snapshot-capture.ts +36 -5
  69. package/src/components/editor/thumbnail-generator.test.ts +654 -0
  70. package/src/components/editor/thumbnail-generator.tsx +1103 -223
  71. package/src/components/editor/use-floorplan-background-placement.ts +34 -12
  72. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  73. package/src/components/editor/views/host-view.tsx +61 -0
  74. package/src/components/editor/views/plugins-view.tsx +663 -0
  75. package/src/components/editor/views/scene-region.tsx +59 -0
  76. package/src/components/editor/views/use-editor-views.ts +57 -0
  77. package/src/components/editor/views/view-bar.test.tsx +54 -0
  78. package/src/components/editor/views/view-bar.tsx +343 -0
  79. package/src/components/editor/wall-justification.test.ts +140 -0
  80. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  81. package/src/components/editor/wall-push-arrow.tsx +92 -0
  82. package/src/components/editor/wall-region-handles.tsx +509 -0
  83. package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
  84. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  85. package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
  86. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  87. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  88. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
  89. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  90. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
  91. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  92. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  93. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  94. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  95. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  96. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  97. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
  98. package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
  99. package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
  100. package/src/components/editor-2d/svg-paths.ts +12 -15
  101. package/src/components/point-ask/bubble.tsx +538 -0
  102. package/src/components/point-ask/hover-chip.tsx +91 -0
  103. package/src/components/point-ask/mode-pill.tsx +92 -0
  104. package/src/components/point-ask/optional-motion.ts +22 -0
  105. package/src/components/point-ask/outline-corners.tsx +93 -0
  106. package/src/components/point-ask/pins.tsx +711 -0
  107. package/src/components/point-ask/point-ask-layer.tsx +83 -0
  108. package/src/components/point-ask/point-ask-styles.ts +105 -0
  109. package/src/components/point-ask/region-marquee-styles.ts +15 -0
  110. package/src/components/point-ask/region-marquee.tsx +64 -0
  111. package/src/components/procedural-items/inspector.tsx +40 -15
  112. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
  113. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  114. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  115. package/src/components/systems/roof/roof-edit-system.tsx +1 -0
  116. package/src/components/systems/zone/zone-system.test.ts +47 -0
  117. package/src/components/systems/zone/zone-system.tsx +29 -68
  118. package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
  119. package/src/components/tools/distribution/connection-compatibility.ts +43 -0
  120. package/src/components/tools/distribution/distribution-public.ts +9 -0
  121. package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
  122. package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
  123. package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
  124. package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
  125. package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
  126. package/src/components/tools/distribution/draw-alignment.ts +35 -0
  127. package/src/components/tools/distribution/ports.test.ts +56 -0
  128. package/src/components/tools/distribution/ports.ts +378 -0
  129. package/src/components/tools/distribution/run-cursor.test.ts +135 -0
  130. package/src/components/tools/distribution/run-cursor.ts +36 -0
  131. package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
  132. package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
  133. package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
  134. package/src/components/tools/distribution/run-port-snap.ts +25 -0
  135. package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
  136. package/src/components/tools/distribution/run-screen-direction.ts +30 -0
  137. package/src/components/tools/elevator/elevator-tool.tsx +3 -6
  138. package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
  139. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
  140. package/src/components/tools/item/placement-history.test.tsx +512 -0
  141. package/src/components/tools/item/placement-strategies.test.ts +46 -1
  142. package/src/components/tools/item/placement-strategies.ts +11 -10
  143. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
  144. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  145. package/src/components/tools/item/use-draft-node.ts +166 -95
  146. package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
  147. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  148. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  149. package/src/components/tools/registry/item-surface-move.test.ts +62 -63
  150. package/src/components/tools/registry/item-surface-move.ts +2 -0
  151. package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
  152. package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
  153. package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
  154. package/src/components/tools/select/box-select-tool.tsx +2 -12
  155. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  156. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  157. package/src/components/tools/select/select-candidates.test.ts +37 -4
  158. package/src/components/tools/select/select-candidates.ts +15 -11
  159. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  160. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  161. package/src/components/tools/tool-manager.tsx +59 -24
  162. package/src/components/tools/wall/wall-drafting.test.ts +175 -47
  163. package/src/components/tools/zone/zone-tool.tsx +4 -12
  164. package/src/components/ui/action-menu/action-button.tsx +9 -1
  165. package/src/components/ui/action-menu/camera-actions.tsx +9 -4
  166. package/src/components/ui/action-menu/control-modes.tsx +15 -42
  167. package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
  168. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
  169. package/src/components/ui/action-menu/index.tsx +56 -33
  170. package/src/components/ui/action-menu/measurement-control.tsx +14 -19
  171. package/src/components/ui/action-menu/placement.ts +20 -0
  172. package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
  173. package/src/components/ui/action-menu/view-toggles.tsx +47 -42
  174. package/src/components/ui/build-panel/build-panel.tsx +242 -0
  175. package/src/components/ui/command-palette/editor-commands.tsx +7 -42
  176. package/src/components/ui/command-palette/index.tsx +50 -39
  177. package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
  178. package/src/components/ui/controls/action-button.tsx +5 -2
  179. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  180. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  181. package/src/components/ui/controls/material-picker.tsx +4 -1
  182. package/src/components/ui/controls/metric-control.tsx +1 -1
  183. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  184. package/src/components/ui/controls/segmented-control.tsx +21 -8
  185. package/src/components/ui/controls/select-control.tsx +65 -0
  186. package/src/components/ui/controls/slider-control.tsx +1 -1
  187. package/src/components/ui/controls/toggle-control.tsx +3 -3
  188. package/src/components/ui/controls/tool-options-panel.tsx +1 -1
  189. package/src/components/ui/floating-level-selector.tsx +159 -59
  190. package/src/components/ui/helpers/building-helper.tsx +4 -1
  191. package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
  192. package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
  193. package/src/components/ui/helpers/helper-manager.tsx +331 -14
  194. package/src/components/ui/helpers/item-helper.tsx +17 -1
  195. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  196. package/src/components/ui/icon-ref.tsx +45 -1
  197. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  198. package/src/components/ui/node-icon-image.tsx +16 -0
  199. package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
  200. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  201. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  202. package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
  203. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
  204. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  205. package/src/components/ui/panels/multi-field-value.ts +6 -3
  206. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  207. package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
  208. package/src/components/ui/panels/node-display.ts +8 -3
  209. package/src/components/ui/panels/panel-manager.tsx +103 -68
  210. package/src/components/ui/panels/panel-wrapper.tsx +130 -62
  211. package/src/components/ui/panels/parametric-field-control.tsx +3 -3
  212. package/src/components/ui/panels/parametric-inspector.tsx +7 -7
  213. package/src/components/ui/panels/reference-panel.tsx +2 -3
  214. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  215. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  216. package/src/components/ui/panels/room-name-options.ts +47 -0
  217. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  218. package/src/components/ui/panels/room-panel.tsx +790 -0
  219. package/src/components/ui/panels/shape-choice.tsx +58 -0
  220. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  221. package/src/components/ui/primitives/badge.tsx +8 -0
  222. package/src/components/ui/primitives/color-dot.tsx +36 -24
  223. package/src/components/ui/primitives/sidebar.tsx +3 -1
  224. package/src/components/ui/right-stack.test.ts +29 -0
  225. package/src/components/ui/right-stack.tsx +164 -0
  226. package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
  227. package/src/components/ui/sidebar/icon-rail.tsx +8 -3
  228. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  229. package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
  230. package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
  231. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
  232. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
  233. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
  234. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
  235. package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
  236. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  237. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
  238. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
  239. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  240. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  241. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  242. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  243. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  244. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  245. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  246. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  247. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  248. package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
  249. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  250. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  251. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  252. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  253. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  254. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  255. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  256. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  257. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  258. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  259. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
  260. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  261. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  262. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  263. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  264. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
  265. package/src/components/ui/sidebar/tab-bar.tsx +14 -4
  266. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  267. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  268. package/src/components/viewer/floorplan-preview.tsx +5 -6
  269. package/src/components/viewer/viewer-controls-bar.tsx +18 -6
  270. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  271. package/src/components/viewer/viewer-rooms.tsx +191 -0
  272. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  273. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  274. package/src/components/viewer/viewer-stage.tsx +2 -2
  275. package/src/components/viewer-zone-system.tsx +3 -8
  276. package/src/hooks/use-auto-save.integration.test.tsx +215 -43
  277. package/src/hooks/use-auto-save.ts +60 -9
  278. package/src/hooks/use-ceiling-events.test.ts +123 -118
  279. package/src/hooks/use-grid-events.test.ts +105 -105
  280. package/src/hooks/use-keyboard.test.ts +243 -19
  281. package/src/hooks/use-keyboard.ts +118 -83
  282. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  283. package/src/hooks/use-open-wall-ends.ts +238 -0
  284. package/src/hooks/use-placement-notice.test.ts +71 -0
  285. package/src/hooks/use-placement-notice.ts +73 -0
  286. package/src/hooks/use-selected-room.ts +143 -0
  287. package/src/index.tsx +312 -58
  288. package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
  289. package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
  290. package/src/lib/add-storey.test.ts +177 -0
  291. package/src/lib/add-storey.ts +59 -0
  292. package/src/lib/below-grade.test.tsx +171 -0
  293. package/src/lib/below-grade.ts +133 -0
  294. package/src/lib/camera-facing.test.ts +24 -0
  295. package/src/lib/camera-facing.ts +24 -0
  296. package/src/lib/ceiling-edit-session.test.ts +321 -0
  297. package/src/lib/ceiling-edit-session.ts +206 -0
  298. package/src/lib/ceiling-region-session.test.ts +179 -0
  299. package/src/lib/continuation.test.ts +19 -1
  300. package/src/lib/continuation.ts +25 -4
  301. package/src/lib/direct-manipulation.ts +2 -0
  302. package/src/lib/door-interaction.ts +14 -0
  303. package/src/lib/download-project-file.ts +17 -0
  304. package/src/lib/editor-level-display.test.ts +96 -0
  305. package/src/lib/editor-level-display.ts +55 -0
  306. package/src/lib/editor-views.ts +178 -0
  307. package/src/lib/export-geometry-wait.test.ts +90 -0
  308. package/src/lib/export-texture-utils.ts +2 -4
  309. package/src/lib/floor-edit-session.ts +153 -0
  310. package/src/lib/floor-footprints.test.ts +563 -0
  311. package/src/lib/floor-footprints.ts +395 -0
  312. package/src/lib/floor-foundation-support.test.ts +284 -0
  313. package/src/lib/floor-opening-draft.ts +218 -0
  314. package/src/lib/floor-openings.test.ts +216 -0
  315. package/src/lib/floor-region-geometry.ts +191 -0
  316. package/src/lib/floor-region-session.test.ts +485 -0
  317. package/src/lib/floor-region-session.ts +589 -0
  318. package/src/lib/floor-region-snap.ts +101 -0
  319. package/src/lib/floor-step-hover.test.ts +32 -0
  320. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  321. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  322. package/src/lib/floorplan/dimension-drive.ts +479 -0
  323. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  324. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  325. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  326. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  327. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  328. package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
  329. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
  330. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
  331. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  332. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  333. package/src/lib/floorplan/geometry.ts +13 -6
  334. package/src/lib/floorplan/index.ts +0 -9
  335. package/src/lib/floorplan/items.ts +5 -2
  336. package/src/lib/floorplan/marks.test.ts +213 -0
  337. package/src/lib/floorplan/marks.ts +238 -0
  338. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  339. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  340. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  341. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  342. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  343. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  344. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  345. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  346. package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
  347. package/src/lib/floorplan/schedules.test.ts +189 -0
  348. package/src/lib/floorplan/schedules.ts +339 -0
  349. package/src/lib/floorplan/sheet-export.test.ts +124 -0
  350. package/src/lib/floorplan/sheet-export.ts +318 -0
  351. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
  352. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  353. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  354. package/src/lib/floorplan/site-plan/coverage.ts +230 -0
  355. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  356. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  357. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  358. package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
  359. package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
  360. package/src/lib/floorplan/site-plan/geometry.ts +142 -0
  361. package/src/lib/floorplan/site-plan/index.ts +55 -0
  362. package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
  363. package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
  364. package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
  365. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  366. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  367. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  368. package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
  369. package/src/lib/floorplan/types.ts +1 -27
  370. package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
  371. package/src/lib/follow-pascal-exterior.test.ts +171 -0
  372. package/src/lib/follow-pascal-exterior.ts +39 -0
  373. package/src/lib/follow-pascal-presentation.test.ts +203 -0
  374. package/src/lib/follow-pascal-presentation.ts +129 -0
  375. package/src/lib/follow-pascal-sight.ts +84 -0
  376. package/src/lib/follow-pascal-targets.test.ts +406 -0
  377. package/src/lib/follow-pascal-targets.ts +198 -0
  378. package/src/lib/follow-pascal.test.ts +350 -0
  379. package/src/lib/follow-pascal.ts +284 -0
  380. package/src/lib/fresh-planar-placement.test.ts +148 -0
  381. package/src/lib/fresh-planar-placement.ts +114 -37
  382. package/src/lib/geometry-script/author.ts +157 -0
  383. package/src/lib/geometry-script/client.ts +159 -0
  384. package/src/lib/geometry-script/finish-scripts.ts +127 -0
  385. package/src/lib/geometry-script/protocol.ts +13 -0
  386. package/src/lib/gesture-lifecycle.test.ts +595 -0
  387. package/src/lib/gesture-lifecycle.ts +290 -0
  388. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  389. package/src/lib/glb-export.test.ts +636 -126
  390. package/src/lib/glb-export.ts +241 -120
  391. package/src/lib/history.test.ts +18 -7
  392. package/src/lib/history.ts +7 -0
  393. package/src/lib/hud-title.ts +109 -0
  394. package/src/lib/hydration-gated-storage.ts +49 -0
  395. package/src/lib/ifc-export.test.ts +174 -0
  396. package/src/lib/ifc-export.ts +201 -0
  397. package/src/lib/ifc-parts.ts +15 -0
  398. package/src/lib/inspector-expanded.test.ts +44 -0
  399. package/src/lib/inspector-expanded.ts +36 -0
  400. package/src/lib/interaction/scope.ts +22 -0
  401. package/src/lib/keyboard-pan.test.ts +129 -7
  402. package/src/lib/keyboard-pan.ts +161 -12
  403. package/src/lib/level-selection.test.ts +57 -0
  404. package/src/lib/level-selection.ts +16 -1
  405. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  406. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  407. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  408. package/src/lib/lot/clean-ring.test.ts +187 -0
  409. package/src/lib/lot/clean-ring.ts +217 -0
  410. package/src/lib/lot/dossier.test.ts +202 -0
  411. package/src/lib/lot/dossier.ts +498 -0
  412. package/src/lib/lot/drop-in.test.ts +326 -0
  413. package/src/lib/lot/drop-in.ts +392 -0
  414. package/src/lib/lot/index.ts +43 -0
  415. package/src/lib/lot/lot-patch.test.ts +340 -0
  416. package/src/lib/lot/lot-patch.ts +394 -0
  417. package/src/lib/lot/parcel-provider.ts +43 -0
  418. package/src/lib/lot/terrain.test.ts +161 -0
  419. package/src/lib/lot/terrain.ts +266 -0
  420. package/src/lib/material-catalog-model.ts +3 -1
  421. package/src/lib/material-paint-panel-model.ts +9 -5
  422. package/src/lib/material-paint.ts +4 -0
  423. package/src/lib/measurement-kind.test.ts +6 -8
  424. package/src/lib/measurements.test.ts +23 -0
  425. package/src/lib/mezzanine-actions.test.ts +342 -0
  426. package/src/lib/mezzanine-draft.test.ts +501 -0
  427. package/src/lib/mezzanine-draft.ts +278 -0
  428. package/src/lib/mezzanine-messages.ts +26 -0
  429. package/src/lib/mezzanine-selection.test.ts +207 -0
  430. package/src/lib/mezzanine-stairs.ts +46 -0
  431. package/src/lib/model-export.ts +3 -1
  432. package/src/lib/node-action-movement.ts +17 -6
  433. package/src/lib/node-display-label.ts +37 -0
  434. package/src/lib/orbit-floor-clearance.test.ts +201 -0
  435. package/src/lib/orbit-floor-clearance.ts +154 -0
  436. package/src/lib/paint-commit-route.test.ts +37 -0
  437. package/src/lib/paint-commit-route.ts +17 -0
  438. package/src/lib/paint-eyedropper.test.tsx +165 -0
  439. package/src/lib/paint-eyedropper.ts +99 -0
  440. package/src/lib/paint-pass-through.test.ts +122 -0
  441. package/src/lib/paint-pass-through.ts +78 -0
  442. package/src/lib/paint-region-hover.ts +25 -0
  443. package/src/lib/paint-region-mode.ts +154 -0
  444. package/src/lib/paint-regions.test.ts +166 -0
  445. package/src/lib/paint-regions.ts +192 -0
  446. package/src/lib/paint-scope.test.ts +317 -35
  447. package/src/lib/paint-scope.ts +267 -25
  448. package/src/lib/panel-rows.ts +21 -0
  449. package/src/lib/parametric-node-update.ts +16 -7
  450. package/src/lib/plate-paint-affected.test.ts +300 -0
  451. package/src/lib/plate-paint-affected.ts +177 -0
  452. package/src/lib/plugin-chat.ts +65 -0
  453. package/src/lib/plugin-panels.test.ts +25 -0
  454. package/src/lib/plugin-panels.ts +94 -0
  455. package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
  456. package/src/lib/point-ask/__fixtures__/house.ts +81 -0
  457. package/src/lib/point-ask/ask-created.test.ts +195 -0
  458. package/src/lib/point-ask/ask-flow.test.ts +380 -0
  459. package/src/lib/point-ask/build-context.test.ts +173 -0
  460. package/src/lib/point-ask/build-context.ts +94 -0
  461. package/src/lib/point-ask/capture-crop.ts +129 -0
  462. package/src/lib/point-ask/choreography.ts +69 -0
  463. package/src/lib/point-ask/compare.test.ts +145 -0
  464. package/src/lib/point-ask/compare.ts +73 -0
  465. package/src/lib/point-ask/describe-target.test.ts +165 -0
  466. package/src/lib/point-ask/describe-target.ts +234 -0
  467. package/src/lib/point-ask/dictation.ts +79 -0
  468. package/src/lib/point-ask/edge-marker.test.ts +61 -0
  469. package/src/lib/point-ask/edge-marker.ts +27 -0
  470. package/src/lib/point-ask/frame-loop.ts +25 -0
  471. package/src/lib/point-ask/history-marks.test.ts +94 -0
  472. package/src/lib/point-ask/history-marks.ts +39 -0
  473. package/src/lib/point-ask/host-api.ts +32 -0
  474. package/src/lib/point-ask/outline-box.test.ts +30 -0
  475. package/src/lib/point-ask/outline-box.ts +11 -0
  476. package/src/lib/point-ask/overlay-metrics.ts +6 -0
  477. package/src/lib/point-ask/pick.test.ts +220 -0
  478. package/src/lib/point-ask/pick.ts +121 -0
  479. package/src/lib/point-ask/pin-machine.test.ts +494 -0
  480. package/src/lib/point-ask/pin-machine.ts +217 -0
  481. package/src/lib/point-ask/placement.test.ts +203 -0
  482. package/src/lib/point-ask/placement.ts +141 -0
  483. package/src/lib/point-ask/pointer-tracker.ts +34 -0
  484. package/src/lib/point-ask/projector.ts +83 -0
  485. package/src/lib/point-ask/region.test.ts +157 -0
  486. package/src/lib/point-ask/region.ts +136 -0
  487. package/src/lib/point-ask/retry.test.ts +217 -0
  488. package/src/lib/point-ask/retry.ts +37 -0
  489. package/src/lib/point-ask/scene-commit.test.ts +312 -0
  490. package/src/lib/point-ask/scene-commit.ts +32 -0
  491. package/src/lib/point-ask/session.ts +1391 -0
  492. package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
  493. package/src/lib/point-ask/spring-keyframes.ts +38 -0
  494. package/src/lib/point-ask/spring.test.ts +63 -0
  495. package/src/lib/point-ask/spring.ts +107 -0
  496. package/src/lib/point-ask/suggestions.test.ts +80 -0
  497. package/src/lib/point-ask/suggestions.ts +125 -0
  498. package/src/lib/point-ask/target-scope.test.ts +622 -0
  499. package/src/lib/point-ask/target-scope.ts +338 -0
  500. package/src/lib/point-ask/types.ts +48 -0
  501. package/src/lib/portable-export.test.ts +34 -1
  502. package/src/lib/portable-export.ts +119 -53
  503. package/src/lib/print-feature-thickness.ts +10 -1
  504. package/src/lib/print-roof-solids.test.ts +39 -1
  505. package/src/lib/print-roof-solids.ts +43 -10
  506. package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
  507. package/src/lib/print-shell-compiler.ts +1 -0
  508. package/src/lib/registered-draft-snap.ts +12 -0
  509. package/src/lib/roof-duplication.ts +10 -2
  510. package/src/lib/room-assembly-overlay.test.ts +275 -0
  511. package/src/lib/room-assembly-overlay.ts +265 -0
  512. package/src/lib/room-built-on.test.ts +379 -0
  513. package/src/lib/room-built-on.ts +138 -0
  514. package/src/lib/room-construction-commands.ts +712 -0
  515. package/src/lib/room-construction.test.ts +746 -0
  516. package/src/lib/room-construction.ts +385 -0
  517. package/src/lib/room-controls-phase6.test.ts +630 -0
  518. package/src/lib/room-divide-session.ts +523 -0
  519. package/src/lib/room-handle-drag.ts +1068 -0
  520. package/src/lib/room-name-catalog.test.ts +90 -0
  521. package/src/lib/room-name-catalog.ts +168 -0
  522. package/src/lib/room-push-dimensions.test.ts +311 -0
  523. package/src/lib/room-push-dimensions.ts +251 -0
  524. package/src/lib/room-selection-commands.ts +62 -0
  525. package/src/lib/room-selection.test.ts +1017 -0
  526. package/src/lib/room-selection.ts +417 -0
  527. package/src/lib/room-structure-commands.ts +94 -0
  528. package/src/lib/room-structure.test.ts +921 -0
  529. package/src/lib/room-transform-session.ts +570 -0
  530. package/src/lib/room-zone-routing.test.ts +294 -0
  531. package/src/lib/room-zone-routing.ts +197 -0
  532. package/src/lib/scene-clipboard.test.ts +610 -1
  533. package/src/lib/scene-clipboard.ts +269 -58
  534. package/src/lib/scene-material-refs.test.ts +79 -0
  535. package/src/lib/scene-material-refs.ts +79 -0
  536. package/src/lib/scene.test.ts +24 -36
  537. package/src/lib/scene.ts +33 -57
  538. package/src/lib/selection-routing.test.ts +129 -45
  539. package/src/lib/selection-routing.ts +54 -75
  540. package/src/lib/session-writes.test.ts +120 -0
  541. package/src/lib/session-writes.ts +205 -0
  542. package/src/lib/sfx-bus.ts +13 -0
  543. package/src/lib/sfx-player.test.ts +70 -9
  544. package/src/lib/sfx-player.ts +6 -0
  545. package/src/lib/sidebar-panel.ts +62 -0
  546. package/src/lib/site-boundary.test.ts +4 -6
  547. package/src/lib/site-boundary.ts +1 -1
  548. package/src/lib/snapping-mode.test.ts +4 -0
  549. package/src/lib/snapping-mode.ts +8 -1
  550. package/src/lib/stair-levels.test.ts +25 -0
  551. package/src/lib/stair-levels.ts +22 -3
  552. package/src/lib/terrace-draft.test.ts +145 -0
  553. package/src/lib/terrace-draft.ts +214 -0
  554. package/src/lib/tool-cursor-icon.test.ts +16 -0
  555. package/src/lib/tool-cursor-icon.ts +11 -0
  556. package/src/lib/typing-target.ts +9 -0
  557. package/src/lib/units.ts +77 -39
  558. package/src/lib/vector-edges.test.ts +58 -0
  559. package/src/lib/vector-edges.ts +400 -0
  560. package/src/lib/viewer-selection.test.ts +577 -0
  561. package/src/lib/viewer-selection.ts +580 -0
  562. package/src/lib/wall-draw-variant.test.ts +71 -0
  563. package/src/lib/wall-draw-variant.ts +91 -0
  564. package/src/lib/wall-paint-region-session.ts +246 -0
  565. package/src/lib/wall-paint-region.test.ts +373 -0
  566. package/src/lib/wall-placement-pointer.test.ts +42 -0
  567. package/src/lib/wall-placement-pointer.ts +57 -0
  568. package/src/lib/wall-polygon-draft.test.ts +262 -0
  569. package/src/lib/wall-polygon-draft.ts +286 -0
  570. package/src/lib/wall-push.test.ts +453 -0
  571. package/src/lib/wall-region-face.ts +352 -0
  572. package/src/lib/wall-region-handles.test.ts +304 -0
  573. package/src/lib/wall-region-handles.ts +368 -0
  574. package/src/lib/wall-region-list.test.ts +119 -0
  575. package/src/lib/wall-region-list.ts +136 -0
  576. package/src/lib/wall-region-snap.ts +110 -0
  577. package/src/lib/window-changes.ts +35 -0
  578. package/src/lib/window-interaction.ts +14 -0
  579. package/src/lib/zone-content.ts +4 -1
  580. package/src/lib/zone-name.test.ts +17 -0
  581. package/src/lib/zone-name.ts +17 -0
  582. package/src/store/opens-in-select-mode.test.ts +223 -0
  583. package/src/store/paint-mode.test.ts +188 -0
  584. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  585. package/src/store/tool-mode.test.ts +137 -6
  586. package/src/store/use-audio.test.ts +146 -0
  587. package/src/store/use-audio.tsx +127 -3
  588. package/src/store/use-construction-reveal.test.ts +173 -0
  589. package/src/store/use-construction-reveal.ts +145 -0
  590. package/src/store/use-corner-snap-hint.test.ts +24 -0
  591. package/src/store/use-corner-snap-hint.ts +47 -0
  592. package/src/store/use-delete-confirmation.ts +28 -1
  593. package/src/store/use-drawing-view.test.ts +24 -3
  594. package/src/store/use-drawing-view.ts +29 -1
  595. package/src/store/use-editor.tsx +432 -321
  596. package/src/store/use-floorplan-draft-preview.ts +13 -0
  597. package/src/store/use-floorplan-marquee.ts +12 -2
  598. package/src/store/use-follow-pascal.test.ts +62 -0
  599. package/src/store/use-follow-pascal.ts +53 -0
  600. package/src/store/use-point-ask.ts +247 -0
  601. package/src/store/use-quick-measurement-hud.ts +3 -3
  602. package/src/store/use-stair-build-preview.ts +33 -5
  603. package/src/store/use-wall-move-ghosts.ts +4 -2
  604. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  605. package/src/store/use-wall-region-handles-selection.ts +139 -0
  606. package/src/store/view-layout.test.ts +201 -0
  607. package/src/store/view-layout.ts +199 -0
  608. package/src/test-utils/immediate-animation-frames.ts +16 -0
  609. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  610. package/src/test-utils/render-current-markup.tsx +16 -0
  611. package/src/test-utils/selection-harness.ts +82 -0
  612. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  613. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  614. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  615. package/src/components/tools/fence/fence-drafting.ts +0 -266
  616. package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
  617. package/src/components/tools/stair/stair-click-guard.ts +0 -72
  618. package/src/components/tools/stair/stair-defaults.ts +0 -18
  619. package/src/components/tools/stair/stair-tool.tsx +0 -634
  620. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  621. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  622. package/src/lib/floorplan/stairs.ts +0 -489
  623. package/src/lib/level-duplication.test.ts +0 -144
  624. package/src/lib/level-duplication.ts +0 -195
@@ -1,9 +1,8 @@
1
1
  import { beforeEach, describe, expect, test } from 'bun:test'
2
- import { nodeRegistry, registerNode, useScene } from '@pascal-app/core'
2
+ import { useScene } from '@pascal-app/core'
3
3
  import { useViewer } from '@pascal-app/viewer'
4
- import { z } from 'zod'
5
4
  import useEditor from '../store/use-editor'
6
- import { normalizeSceneGraphNodes, syncEditorSelectionFromCurrentScene } from './scene'
5
+ import { syncEditorSelectionFromCurrentScene } from './scene'
7
6
 
8
7
  const building = {
9
8
  children: ['level_scene-root'],
@@ -59,41 +58,30 @@ describe('scene selection synchronization', () => {
59
58
  buildingId: building.id,
60
59
  levelId: level.id,
61
60
  })
62
- expect(useEditor.getState().phase).toBe('structure')
61
+ expect(useEditor.getState().phase).toBe('building')
63
62
  })
64
- })
65
63
 
66
- describe('scene graph normalization', () => {
67
- test('materializes registered schema defaults before the graph reaches renderers', () => {
68
- const restoreRegistry = nodeRegistry._snapshot()
69
- try {
70
- registerNode({
71
- kind: 'test-scene-normalization',
72
- schema: z.object({
73
- id: z.string(),
74
- position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
75
- rotation: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
76
- type: z.literal('test-scene-normalization'),
77
- }),
78
- schemaVersion: 1,
79
- } as never)
64
+ test('opening another project disarms the tool the last one left armed', () => {
65
+ // This runs on every scene load, so a mid-session project switch reads the
66
+ // live state of the project the user just left. A project always opens in
67
+ // select mode — the first click on the new scene selects, it does not build.
68
+ useScene.setState({
69
+ nodes: {
70
+ [building.id]: building,
71
+ [level.id]: level,
72
+ [wall.id]: wall,
73
+ },
74
+ rootNodeIds: [building.id],
75
+ } as never)
76
+ useEditor.getState().setPhase('building')
77
+ useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' })
78
+
79
+ syncEditorSelectionFromCurrentScene()
80
80
 
81
- expect(
82
- normalizeSceneGraphNodes({
83
- test: { id: 'test', type: 'test-scene-normalization' },
84
- unknown: { id: 'unknown', type: 'unknown-kind', custom: true },
85
- }),
86
- ).toEqual({
87
- test: {
88
- id: 'test',
89
- position: [0, 0, 0],
90
- rotation: [0, 0, 0],
91
- type: 'test-scene-normalization',
92
- },
93
- unknown: { id: 'unknown', type: 'unknown-kind', custom: true },
94
- })
95
- } finally {
96
- restoreRegistry()
97
- }
81
+ expect(useEditor.getState().toolMode).toEqual({ mode: 'select' })
82
+ expect(useEditor.getState().mode).toBe('select')
83
+ expect(useEditor.getState().tool).toBeNull()
84
+ // The phase is a preference and stays put.
85
+ expect(useEditor.getState().phase).toBe('building')
98
86
  })
99
87
  })
package/src/lib/scene.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  'use client'
2
2
 
3
3
  import {
4
+ type AnyNodeId,
4
5
  clearSceneHistory,
6
+ getSceneDocument,
7
+ materializeRegisteredNodeDefaults,
5
8
  nodeRegistry,
6
9
  resolveLevelId,
7
10
  sceneRegistry,
@@ -9,10 +12,11 @@ import {
9
12
  } from '@pascal-app/core'
10
13
  import { useViewer } from '@pascal-app/viewer'
11
14
  import useEditor, {
15
+ editorUiStateOnOpen,
12
16
  hasCustomPersistedEditorUiState,
13
- normalizePersistedEditorUiState,
14
17
  type PersistedEditorUiState,
15
18
  } from '../store/use-editor'
19
+ import useInteractionScope from '../store/use-interaction-scope'
16
20
  import { editorHostPanelRegistry } from './plugin-panels'
17
21
 
18
22
  export type SceneGraph = {
@@ -25,6 +29,18 @@ export type SceneGraph = {
25
29
  installedPlugins?: string[]
26
30
  }
27
31
 
32
+ /**
33
+ * The scene as saves and exports write it. A clone in hand — a preset or duplicate the user is
34
+ * placing — is in the store from the tile click so tools can render and host it, but it only
35
+ * becomes part of the document when its placement commits.
36
+ */
37
+ export function getSavedSceneDocument() {
38
+ const { scope } = useInteractionScope.getState()
39
+ const placing =
40
+ scope.kind === 'placing' && scope.driver === 'move-tool' ? [scope.nodeId as AnyNodeId] : []
41
+ return getSceneDocument(placing)
42
+ }
43
+
28
44
  type PersistedSelectionPath = {
29
45
  buildingId: string | null
30
46
  levelId: string | null
@@ -150,7 +166,6 @@ export function writePersistedSelection(selection: {
150
166
  }
151
167
 
152
168
  function getEditorUiStateForRestoredSelection(
153
- sceneNodes: Record<string, any>,
154
169
  selection: PersistedSelectionPath,
155
170
  fallbackUiState: PersistedEditorUiState,
156
171
  ): PersistedEditorUiState {
@@ -167,43 +182,16 @@ function getEditorUiStateForRestoredSelection(
167
182
  : { mode: 'select' },
168
183
  mode,
169
184
  tool: mode === 'build' ? 'property-line' : null,
170
- structureLayer: 'elements',
171
- catalogCategory: null,
172
- }
173
- }
174
-
175
- if (selection.zoneId) {
176
- return {
177
- ...fallbackUiState,
178
- phase: 'structure',
179
- toolMode: { mode: 'select' },
180
- mode: 'select',
181
- tool: null,
182
- structureLayer: 'zones',
183
185
  catalogCategory: null,
184
186
  }
185
187
  }
186
188
 
187
- const selectedNodes = selection.selectedIds
188
- .map((id) => sceneNodes[id])
189
- .filter((node): node is Record<string, any> => Boolean(node))
190
-
191
- const shouldRestoreFurnishPhase =
192
- selectedNodes.length > 0 &&
193
- selectedNodes.every(
194
- (node) =>
195
- node.type === 'item' &&
196
- node.asset?.category !== 'door' &&
197
- node.asset?.category !== 'window',
198
- )
199
-
200
189
  return {
201
190
  ...fallbackUiState,
202
- phase: shouldRestoreFurnishPhase ? 'furnish' : 'structure',
191
+ phase: 'building',
203
192
  toolMode: { mode: 'select' },
204
193
  mode: 'select',
205
194
  tool: null,
206
- structureLayer: 'elements',
207
195
  catalogCategory: null,
208
196
  }
209
197
  }
@@ -284,11 +272,14 @@ export function syncEditorSelectionFromCurrentScene() {
284
272
  .flatMap((node) => (Array.isArray(node.children) ? node.children.map(resolve) : []))
285
273
  .find((node) => node?.type === 'building')
286
274
  const firstLevel = firstBuilding?.children?.map(resolve).find((n: any) => n?.type === 'level')
287
- const restoredEditorUiState = normalizePersistedEditorUiState(useEditor.getState())
275
+ // Every project open lands in select mode, a mid-session switch included: this
276
+ // runs on each scene load, so it reads the live state the last project left
277
+ // behind and would otherwise re-arm that project's tool over the new scene.
278
+ const restoredEditorUiState = editorUiStateOnOpen(useEditor.getState())
288
279
  const shouldRestoreEditorUiState = hasCustomPersistedEditorUiState(restoredEditorUiState)
289
280
  const restoredSelection = getRestoredSelectionForScene(sceneNodes)
290
281
  const selectionDrivenEditorUiState = restoredSelection
291
- ? getEditorUiStateForRestoredSelection(sceneNodes, restoredSelection, restoredEditorUiState)
282
+ ? getEditorUiStateForRestoredSelection(restoredSelection, restoredEditorUiState)
292
283
  : null
293
284
 
294
285
  if (firstBuilding && firstLevel) {
@@ -303,8 +294,7 @@ export function syncEditorSelectionFromCurrentScene() {
303
294
  selectedIds: [],
304
295
  zoneId: null,
305
296
  })
306
- useEditor.getState().setPhase('structure')
307
- useEditor.getState().setStructureLayer('elements')
297
+ useEditor.getState().setPhase('building')
308
298
  useEditor.getState().setMode('build')
309
299
  useEditor.getState().setTool('wall')
310
300
  return
@@ -351,8 +341,7 @@ export function syncEditorSelectionFromCurrentScene() {
351
341
  selectedIds: [],
352
342
  zoneId: null,
353
343
  })
354
- useEditor.getState().setPhase('structure')
355
- useEditor.getState().setStructureLayer('elements')
344
+ useEditor.getState().setPhase('building')
356
345
  } else {
357
346
  useEditor.getState().setPhase('site')
358
347
  useViewer.getState().setSelection({
@@ -381,7 +370,6 @@ function resetEditorInteractionState() {
381
370
  sceneRegistry.clear()
382
371
  useEditor.setState({
383
372
  phase: 'site',
384
- structureLayer: 'elements',
385
373
  catalogCategory: null,
386
374
  selectedItem: null,
387
375
  selectedReferenceId: null,
@@ -400,30 +388,18 @@ function hasUsableSceneGraph(sceneGraph?: SceneGraph | null): sceneGraph is Scen
400
388
  )
401
389
  }
402
390
 
403
- export function normalizeSceneGraphNodes(
404
- nodes: Readonly<Record<string, unknown>>,
405
- ): Record<string, unknown> {
406
- return Object.fromEntries(
407
- Object.entries(nodes).map(([id, value]) => {
408
- if (!value || typeof value !== 'object' || Array.isArray(value)) return [id, value]
409
- const type = (value as { type?: unknown }).type
410
- if (typeof type !== 'string') return [id, value]
411
- const parsed = nodeRegistry.get(type)?.schema.safeParse(value)
412
- return [id, parsed?.success ? parsed.data : value]
413
- }),
414
- )
415
- }
416
-
417
391
  export function applySceneGraphToEditor(sceneGraph?: SceneGraph | null) {
418
392
  const defaultInstalledPlugins = editorHostPanelRegistry.getDefaultInstalledPluginIds()
419
393
  if (hasUsableSceneGraph(sceneGraph)) {
420
394
  const { nodes, rootNodeIds, collections, materials, installedPlugins } = sceneGraph
421
- useScene.getState().setScene(normalizeSceneGraphNodes(nodes) as any, rootNodeIds as any, {
422
- collections: collections as any,
423
- materials: materials as any,
424
- installedPlugins: installedPlugins ?? defaultInstalledPlugins,
425
- hasExplicitPluginInstallState: installedPlugins !== undefined,
426
- })
395
+ useScene
396
+ .getState()
397
+ .setScene(materializeRegisteredNodeDefaults(nodes) as any, rootNodeIds as any, {
398
+ collections: collections as any,
399
+ materials: materials as any,
400
+ installedPlugins: installedPlugins ?? defaultInstalledPlugins,
401
+ hasExplicitPluginInstallState: installedPlugins !== undefined,
402
+ })
427
403
  } else {
428
404
  useScene.getState().clearScene()
429
405
  useScene.getState().setInstalledPlugins(defaultInstalledPlugins, { explicit: false })
@@ -11,14 +11,109 @@ import { useViewer } from '@pascal-app/viewer'
11
11
  import { z } from 'zod'
12
12
  import useEditor from '../store/use-editor'
13
13
  import {
14
+ deleteNodeFromCanvas,
14
15
  emitCanvasNodeSelection,
16
+ enterBuildingFromCanvas,
17
+ enterBuildingFromPlanHit,
18
+ resolveCanvasBuildingId,
15
19
  resolveCanvasSelectionNode,
16
- resolveNodeSelectionTarget,
17
20
  resolveSelectedIdsForNodeClick,
18
21
  selectionModifiersFromEvent,
19
22
  shouldPreserveSelectedRoofHostTarget,
20
23
  } from './selection-routing'
21
24
 
25
+ describe('building entry from either canvas', () => {
26
+ test('a Site hit enters the owning building before selecting an element', () => {
27
+ const previousScene = useScene.getState()
28
+ const previousSelection = useViewer.getState().selection
29
+ const previousEditor = useEditor.getState()
30
+ const building = { id: 'building_pick', type: 'building', children: ['level_pick'] }
31
+ const level = {
32
+ id: 'level_pick',
33
+ type: 'level',
34
+ level: 0,
35
+ parentId: building.id,
36
+ children: ['wall_pick'],
37
+ }
38
+ const wall = { id: 'wall_pick', type: 'wall', parentId: level.id }
39
+ const shelf = { id: 'shelf_pick', type: 'shelf', parentId: wall.id }
40
+ const elevator = { id: 'elevator_pick', type: 'elevator', parentId: building.id }
41
+ const nodes = Object.fromEntries(
42
+ [building, level, wall, shelf, elevator].map((node) => [node.id, node]),
43
+ ) as Record<string, AnyNode>
44
+ try {
45
+ useScene.setState({ nodes: nodes as never, rootNodeIds: [building.id as never] })
46
+ for (const hit of [wall, shelf, elevator]) {
47
+ useEditor.getState().setPhase('site')
48
+ useEditor.getState().armToolMode({ mode: 'select' })
49
+ const node = nodes[hit.id]!
50
+ expect(resolveCanvasBuildingId(node, nodes)).toBe(building.id as never)
51
+ expect(enterBuildingFromCanvas(node)).toBe(true)
52
+ expect(useEditor.getState().phase).toBe('building')
53
+ expect(useViewer.getState().selection).toMatchObject({
54
+ buildingId: building.id,
55
+ levelId: level.id,
56
+ selectedIds: [],
57
+ })
58
+ useViewer.getState().setSelection({ selectedIds: [wall.id as never] })
59
+ expect(enterBuildingFromCanvas(node)).toBe(false)
60
+ expect(useViewer.getState().selection.selectedIds).toEqual([wall.id])
61
+ }
62
+ useEditor.getState().setPhase('site')
63
+ useEditor.getState().armToolMode({ mode: 'build', tool: 'item' })
64
+ expect(enterBuildingFromCanvas(nodes[wall.id]!)).toBe(false)
65
+ expect(useEditor.getState().phase).toBe('site')
66
+ } finally {
67
+ useScene.setState(previousScene)
68
+ useViewer.setState({ selection: previousSelection })
69
+ useEditor.setState(previousEditor)
70
+ }
71
+ })
72
+
73
+ test('a 2D plan click from Site on a wall picks the whole building; bare ground stays in Site', () => {
74
+ const previousScene = useScene.getState()
75
+ const previousSelection = useViewer.getState().selection
76
+ const previousEditor = useEditor.getState()
77
+ const building = { id: 'building_plan', type: 'building', children: ['level_plan'] }
78
+ const level = { id: 'level_plan', type: 'level', level: 0, parentId: building.id, children: [] }
79
+ const wall = { id: 'wall_plan', type: 'wall', parentId: level.id }
80
+ const spawn = { id: 'spawn_plan', type: 'spawn', parentId: level.id }
81
+ const ground = { id: 'surface-material_plan', type: 'surface-material', parentId: 'site_plan' }
82
+ const siteFence = { id: 'fence_plan', type: 'fence', parentId: 'site_plan' }
83
+ const nodes = Object.fromEntries(
84
+ [building, level, wall, spawn, ground, siteFence].map((node) => [node.id, node]),
85
+ ) as Record<string, AnyNode>
86
+ try {
87
+ useScene.setState({ nodes: nodes as never, rootNodeIds: [building.id as never] })
88
+ useViewer.getState().setSelection({ buildingId: null })
89
+ useEditor.getState().setPhase('site')
90
+ useEditor.getState().armToolMode({ mode: 'select' })
91
+ for (const hit of [null, 'missing_plan', ground.id, siteFence.id]) {
92
+ expect(enterBuildingFromPlanHit(hit)).toBe(false)
93
+ expect(useEditor.getState().phase).toBe('site')
94
+ }
95
+ expect(enterBuildingFromPlanHit(wall.id)).toBe(true)
96
+ expect(useEditor.getState().phase).toBe('building')
97
+ expect(useViewer.getState().selection).toMatchObject({
98
+ buildingId: building.id,
99
+ selectedIds: [],
100
+ })
101
+ // Inside the building the same hit is an ordinary selection again.
102
+ expect(enterBuildingFromPlanHit(wall.id)).toBe(false)
103
+ useEditor.getState().setPhase('site')
104
+ expect(enterBuildingFromPlanHit(spawn.id)).toBe(true)
105
+ expect(useViewer.getState().selection).toMatchObject({
106
+ buildingId: building.id,
107
+ selectedIds: [],
108
+ })
109
+ } finally {
110
+ useScene.setState(previousScene)
111
+ useViewer.setState({ selection: previousSelection })
112
+ useEditor.setState(previousEditor)
113
+ }
114
+ })
115
+ })
116
+
22
117
  function registerTestDefinition(kind: string, overrides: Record<string, unknown> = {}) {
23
118
  if (nodeRegistry.has(kind)) return
24
119
  registerNode({
@@ -69,30 +164,50 @@ describe('resolveSelectedIdsForNodeClick', () => {
69
164
  })
70
165
 
71
166
  describe('emitCanvasNodeSelection', () => {
72
- test('publishes the accepted canvas node once', () => {
167
+ // Plugins (the WebXR plugin) listen for canvas selections on this event.
168
+ test('a canvas node click publishes the node once', () => {
73
169
  const node = { id: 'wall_1', type: 'wall' } as unknown as AnyNode
74
170
  const received: AnyNode[] = []
75
171
  const onSelection = (selectedNode: AnyNode) => received.push(selectedNode)
76
172
  emitter.on('selection:canvas-node-click', onSelection)
77
-
78
- emitCanvasNodeSelection(node)
79
-
80
- emitter.off('selection:canvas-node-click', onSelection)
173
+ try {
174
+ emitCanvasNodeSelection(node)
175
+ } finally {
176
+ emitter.off('selection:canvas-node-click', onSelection)
177
+ }
81
178
  expect(received).toEqual([node])
82
179
  })
83
180
 
84
- test('deletes an accepted floorplan node when Delete mode is active', () => {
85
- const node = BlockNode.parse({ id: 'block_floorplan-delete-target' })
181
+ test('in Delete mode the click deletes the node and publishes nothing', () => {
182
+ const node = BlockNode.parse({ id: 'block_canvas-delete-target' })
86
183
  const previousToolMode = useEditor.getState().toolMode
87
184
  const previousScene = useScene.getState()
88
185
  const previousSelection = useViewer.getState().selection
89
186
  const received: AnyNode[] = []
90
- const listener = (selectedNode: AnyNode) => received.push(selectedNode)
187
+ const onSelection = (selectedNode: AnyNode) => received.push(selectedNode)
188
+ emitter.on('selection:canvas-node-click', onSelection)
189
+ try {
190
+ useEditor.getState().armToolMode({ mode: 'delete' })
191
+ useScene.setState({ nodes: { [node.id]: node }, rootNodeIds: [node.id], readOnly: false })
192
+ emitCanvasNodeSelection(node)
193
+ expect(useScene.getState().nodes[node.id]).toBeUndefined()
194
+ expect(received).toEqual([])
195
+ } finally {
196
+ emitter.off('selection:canvas-node-click', onSelection)
197
+ useEditor.getState().armToolMode(previousToolMode)
198
+ useScene.setState(previousScene)
199
+ useViewer.setState({ selection: previousSelection })
200
+ }
201
+ })
202
+ })
91
203
 
92
- emitter.on('selection:canvas-node-click', listener)
204
+ describe('deleteNodeFromCanvas', () => {
205
+ test('deletes the clicked plan node and clears the selection', () => {
206
+ const node = BlockNode.parse({ id: 'block_floorplan-delete-target' })
207
+ const previousScene = useScene.getState()
208
+ const previousSelection = useViewer.getState().selection
93
209
 
94
210
  try {
95
- useEditor.getState().armToolMode({ mode: 'delete' })
96
211
  useScene.setState({
97
212
  nodes: { [node.id]: node },
98
213
  rootNodeIds: [node.id],
@@ -100,27 +215,22 @@ describe('emitCanvasNodeSelection', () => {
100
215
  })
101
216
  useViewer.getState().setSelection({ selectedIds: [node.id] })
102
217
 
103
- emitCanvasNodeSelection(node)
218
+ deleteNodeFromCanvas(node)
104
219
 
105
220
  expect(useScene.getState().nodes[node.id]).toBeUndefined()
106
221
  expect(useViewer.getState().selection.selectedIds).toEqual([])
107
- expect(received).toEqual([])
108
222
  } finally {
109
- emitter.off('selection:canvas-node-click', listener)
110
- useEditor.getState().armToolMode(previousToolMode)
111
223
  useScene.setState(previousScene)
112
224
  useViewer.setState({ selection: previousSelection })
113
225
  }
114
226
  })
115
227
 
116
- test('preserves a floorplan node and its selection when the scene is read-only', () => {
228
+ test('preserves a plan node and its selection when the scene is read-only', () => {
117
229
  const node = BlockNode.parse({ id: 'block_floorplan-read-only-target' })
118
- const previousToolMode = useEditor.getState().toolMode
119
230
  const previousScene = useScene.getState()
120
231
  const previousSelection = useViewer.getState().selection
121
232
 
122
233
  try {
123
- useEditor.getState().armToolMode({ mode: 'delete' })
124
234
  useScene.setState({
125
235
  nodes: { [node.id]: node },
126
236
  rootNodeIds: [node.id],
@@ -128,12 +238,11 @@ describe('emitCanvasNodeSelection', () => {
128
238
  })
129
239
  useViewer.getState().setSelection({ selectedIds: [node.id] })
130
240
 
131
- emitCanvasNodeSelection(node)
241
+ deleteNodeFromCanvas(node)
132
242
 
133
243
  expect(useScene.getState().nodes[node.id]).toEqual(node)
134
244
  expect(useViewer.getState().selection.selectedIds).toEqual([node.id])
135
245
  } finally {
136
- useEditor.getState().armToolMode(previousToolMode)
137
246
  useScene.setState(previousScene)
138
247
  useViewer.setState({ selection: previousSelection })
139
248
  }
@@ -167,31 +276,6 @@ describe('selectionModifiersFromEvent', () => {
167
276
  })
168
277
  })
169
278
 
170
- describe('resolveNodeSelectionTarget', () => {
171
- test('routes furniture items to furnish', () => {
172
- const node = {
173
- id: 'item_1',
174
- type: 'item',
175
- asset: { category: 'furniture' },
176
- } as unknown as AnyNode
177
-
178
- expect(resolveNodeSelectionTarget(node)).toEqual({ phase: 'furnish' })
179
- })
180
-
181
- test('routes door and window catalog items to structure', () => {
182
- const node = {
183
- id: 'item_1',
184
- type: 'item',
185
- asset: { category: 'door' },
186
- } as unknown as AnyNode
187
-
188
- expect(resolveNodeSelectionTarget(node)).toEqual({
189
- phase: 'structure',
190
- structureLayer: 'elements',
191
- })
192
- })
193
- })
194
-
195
279
  describe('resolveCanvasSelectionNode', () => {
196
280
  // Mirrors the cabinet-module setup: modules proxy to their run for grouped
197
281
  // move / rotate, but declare `selectionProxy.bypassDirectPick` so a direct
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  type AnyNode,
3
3
  type AnyNodeId,
4
+ type BuildingNode,
4
5
  emitter,
5
- type ItemNode,
6
6
  nodeRegistry,
7
7
  resolveSelectionProxyId,
8
8
  useScene,
@@ -11,6 +11,38 @@ import { useViewer } from '@pascal-app/viewer'
11
11
  import useEditor from '../store/use-editor'
12
12
  import { emitDeleteSFX } from './sfx-bus'
13
13
 
14
+ export function resolveCanvasBuildingId(
15
+ node: AnyNode,
16
+ nodes: Record<string, AnyNode>,
17
+ ): BuildingNode['id'] | null {
18
+ const visited = new Set<string>()
19
+ let current: AnyNode | undefined = node
20
+ while (current && !visited.has(current.id)) {
21
+ if (current.type === 'building') return current.id
22
+ visited.add(current.id)
23
+ current = current.parentId ? nodes[current.parentId] : undefined
24
+ }
25
+ return null
26
+ }
27
+
28
+ export function enterBuildingFromCanvas(node: AnyNode): boolean {
29
+ if (useEditor.getState().phase !== 'site' || useEditor.getState().mode !== 'select') return false
30
+ const buildingId = resolveCanvasBuildingId(node, useScene.getState().nodes)
31
+ if (!buildingId) return false
32
+ useViewer.getState().setSelection({ buildingId, selectedIds: [] })
33
+ useEditor.getState().setPhase('building')
34
+ return true
35
+ }
36
+
37
+ /**
38
+ * A floor-plan click from site, entry or background hit alike: a hit on any of
39
+ * a building's elements picks that building and goes inside, as in 3D.
40
+ */
41
+ export function enterBuildingFromPlanHit(hitId: string | null): boolean {
42
+ const node = hitId ? useScene.getState().nodes[hitId as AnyNodeId] : undefined
43
+ return node ? enterBuildingFromCanvas(node) : false
44
+ }
45
+
14
46
  export type SelectionModifierKeys = {
15
47
  meta: boolean
16
48
  ctrl: boolean
@@ -19,29 +51,33 @@ export type SelectionModifierKeys = {
19
51
  alt: boolean
20
52
  }
21
53
 
22
- export type NodeSelectionTarget = {
23
- phase: 'site' | 'structure' | 'furnish'
24
- structureLayer?: 'zones' | 'elements'
25
- }
26
-
54
+ /** A Delete-mode click on a plan entry: the sledgehammer, not a selection. */
55
+ /**
56
+ * A canvas click (2D or 3D) selected `node`: publish it on
57
+ * `selection:canvas-node-click`, which plugins listen for. In Delete mode the
58
+ * click deletes the node instead.
59
+ */
27
60
  export function emitCanvasNodeSelection(node: AnyNode): void {
28
61
  if (useEditor.getState().mode === 'delete') {
29
- const scene = useScene.getState()
30
- if (scene.readOnly) return
31
-
32
- emitDeleteSFX(node.type)
33
- scene.deleteNode(node.id as AnyNodeId)
34
- if (node.parentId) scene.dirtyNodes.add(node.parentId as AnyNodeId)
35
- useViewer.getState().setSelection({ selectedIds: [] })
36
- if (useViewer.getState().hoveredId === node.id) {
37
- useViewer.setState({ hoveredId: null })
38
- }
62
+ deleteNodeFromCanvas(node)
39
63
  return
40
64
  }
41
-
42
65
  emitter.emit('selection:canvas-node-click', node)
43
66
  }
44
67
 
68
+ export function deleteNodeFromCanvas(node: AnyNode): void {
69
+ const scene = useScene.getState()
70
+ if (scene.readOnly) return
71
+
72
+ emitDeleteSFX(node.type)
73
+ scene.deleteNode(node.id as AnyNodeId)
74
+ if (node.parentId) scene.dirtyNodes.add(node.parentId as AnyNodeId)
75
+ useViewer.getState().setSelection({ selectedIds: [] })
76
+ if (useViewer.getState().hoveredId === node.id) {
77
+ useViewer.setState({ hoveredId: null })
78
+ }
79
+ }
80
+
45
81
  function shouldBypassSelectionProxy(node: AnyNode, target: AnyNode): boolean {
46
82
  if (node.id === target.id) return false
47
83
  // Kind-declared bypass (`def.selectionProxy.bypassDirectPick`): the kind
@@ -62,7 +98,7 @@ export function resolveCanvasSelectionNode({
62
98
  const proxiedTarget = nodes[resolveSelectionProxyId(node, nodes)] ?? node
63
99
  let target = shouldBypassSelectionProxy(node, proxiedTarget) ? node : proxiedTarget
64
100
  const parentFrame = nodeRegistry.get(target.type)?.capabilities?.movable?.parentFrame
65
- if (parentFrame) {
101
+ if (parentFrame && !parentFrame.independent) {
66
102
  const parent = parentFrame.resolveParent(target, nodes as Readonly<Record<string, AnyNode>>)
67
103
  if (parent && selectedIds.length === 1 && selectedIds[0] === parent.id) {
68
104
  target = parent
@@ -154,60 +190,3 @@ export function shouldPreserveSelectedRoofHostTarget({
154
190
  selectedIds[0] === node.id
155
191
  )
156
192
  }
157
-
158
- export function resolveNodeSelectionTarget(node: AnyNode): NodeSelectionTarget | null {
159
- if (node.type === 'building') {
160
- return { phase: 'site' }
161
- }
162
-
163
- if (node.type === 'zone') {
164
- return {
165
- phase: 'structure',
166
- structureLayer: 'zones',
167
- }
168
- }
169
-
170
- if (node.type === 'item') {
171
- const item = node as ItemNode
172
- if (item.asset.category === 'door' || item.asset.category === 'window') {
173
- return {
174
- phase: 'structure',
175
- structureLayer: 'elements',
176
- }
177
- }
178
- return { phase: 'furnish' }
179
- }
180
-
181
- if (
182
- node.type === 'wall' ||
183
- node.type === 'fence' ||
184
- node.type === 'column' ||
185
- node.type === 'elevator' ||
186
- node.type === 'slab' ||
187
- node.type === 'ceiling' ||
188
- node.type === 'roof' ||
189
- node.type === 'roof-segment' ||
190
- node.type === 'stair' ||
191
- node.type === 'stair-segment' ||
192
- node.type === 'spawn' ||
193
- node.type === 'window' ||
194
- node.type === 'door'
195
- ) {
196
- return {
197
- phase: 'structure',
198
- structureLayer: 'elements',
199
- }
200
- }
201
-
202
- const def = nodeRegistry.get(node.type)
203
- if (!def) return null
204
-
205
- if (def.category === 'furnish') {
206
- return { phase: 'furnish' }
207
- }
208
-
209
- return {
210
- phase: 'structure',
211
- structureLayer: 'elements',
212
- }
213
- }