@pascal-app/editor 1.0.1 → 1.0.4

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 (498) hide show
  1. package/package.json +16 -9
  2. package/src/components/editor/action-menu-button.tsx +83 -0
  3. package/src/components/editor/bake-thumbnail.tsx +315 -4
  4. package/src/components/editor/capture-level-picker.tsx +72 -0
  5. package/src/components/editor/custom-camera-controls.tsx +221 -128
  6. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  7. package/src/components/editor/duplicate-node.ts +244 -0
  8. package/src/components/editor/editor-layout-mobile.tsx +37 -25
  9. package/src/components/editor/editor-layout-v2.tsx +131 -58
  10. package/src/components/editor/export-manager.tsx +34 -8
  11. package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
  12. package/src/components/editor/first-person/build-collider-world.ts +6 -2
  13. package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
  14. package/src/components/editor/first-person/mechanism-targets.ts +36 -0
  15. package/src/components/editor/first-person-controls.tsx +205 -2
  16. package/src/components/editor/floating-action-menu.tsx +63 -207
  17. package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
  18. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  19. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  20. package/src/components/editor/floor-region-controls.tsx +846 -0
  21. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  22. package/src/components/editor/floor-surface.test.ts +165 -0
  23. package/src/components/editor/floor-surface.ts +182 -0
  24. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  25. package/src/components/editor/floorplan-background-selection.ts +1 -3
  26. package/src/components/editor/floorplan-panel.tsx +536 -487
  27. package/src/components/editor/footprint-height-handle.tsx +204 -0
  28. package/src/components/editor/grid.tsx +31 -7
  29. package/src/components/editor/group-actions.ts +59 -11
  30. package/src/components/editor/group-floating-action-menu.tsx +19 -9
  31. package/src/components/editor/group-move-3d.ts +4 -6
  32. package/src/components/editor/group-rotate-handle.tsx +81 -35
  33. package/src/components/editor/group-selection-box-3d.tsx +25 -17
  34. package/src/components/editor/group-transform-shared.test.ts +126 -1
  35. package/src/components/editor/group-transform-shared.ts +79 -73
  36. package/src/components/editor/handles/handle-arrow.tsx +72 -2
  37. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  38. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  39. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  40. package/src/components/editor/handles/use-handle-drag.ts +272 -131
  41. package/src/components/editor/index.tsx +316 -176
  42. package/src/components/editor/join-walls-pill.tsx +44 -0
  43. package/src/components/editor/node-action-menu.tsx +70 -70
  44. package/src/components/editor/node-arrow-handles.tsx +167 -14
  45. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  46. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  47. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  48. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  49. package/src/components/editor/registry-action-contributions.tsx +132 -0
  50. package/src/components/editor/room-controls.tsx +847 -0
  51. package/src/components/editor/room-floor-highlight.tsx +128 -0
  52. package/src/components/editor/room-handles-2d.tsx +354 -0
  53. package/src/components/editor/room-handles.tsx +675 -0
  54. package/src/components/editor/room-highlight.tsx +225 -0
  55. package/src/components/editor/room-labels.test.ts +357 -0
  56. package/src/components/editor/room-labels.tsx +990 -0
  57. package/src/components/editor/room-transform-ghost.tsx +256 -0
  58. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  59. package/src/components/editor/selection-manager.tsx +451 -317
  60. package/src/components/editor/slab-hole-highlights.tsx +3 -0
  61. package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
  62. package/src/components/editor/snapshot-capture.test.ts +52 -4
  63. package/src/components/editor/snapshot-capture.ts +30 -5
  64. package/src/components/editor/thumbnail-generator.test.ts +308 -0
  65. package/src/components/editor/thumbnail-generator.tsx +1035 -229
  66. package/src/components/editor/use-floorplan-background-placement.ts +31 -11
  67. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  68. package/src/components/editor/views/host-view.tsx +61 -0
  69. package/src/components/editor/views/plugins-view.tsx +670 -0
  70. package/src/components/editor/views/scene-region.tsx +59 -0
  71. package/src/components/editor/views/use-editor-views.ts +57 -0
  72. package/src/components/editor/views/view-bar.tsx +220 -0
  73. package/src/components/editor/wall-justification.test.ts +140 -0
  74. package/src/components/editor/wall-move-side-handles.tsx +114 -163
  75. package/src/components/editor/wall-opening-highlights.tsx +2 -3
  76. package/src/components/editor/wall-push-arrow.tsx +92 -0
  77. package/src/components/editor/wall-region-handles.tsx +509 -0
  78. package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
  79. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  80. package/src/components/editor-2d/floorplan-group-move.tsx +17 -24
  81. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  82. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  83. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +33 -12
  84. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  85. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +242 -126
  86. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  87. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  88. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  89. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +166 -0
  90. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  91. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +87 -8
  92. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +355 -245
  93. package/src/components/editor-2d/svg-paths.ts +12 -15
  94. package/src/components/procedural-items/inspector.tsx +40 -15
  95. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  96. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  97. package/src/components/systems/zone/zone-system.test.ts +47 -0
  98. package/src/components/systems/zone/zone-system.tsx +31 -66
  99. package/src/components/tools/__fixtures__/hosting.ts +10 -0
  100. package/src/components/tools/item/item-surface-placement.test.ts +3 -9
  101. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +3 -9
  102. package/src/components/tools/item/placement-history.test.tsx +474 -0
  103. package/src/components/tools/item/placement-strategies.ts +63 -47
  104. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +3 -3
  105. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  106. package/src/components/tools/item/use-draft-node.ts +207 -97
  107. package/src/components/tools/item/use-placement-coordinator.tsx +251 -111
  108. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  109. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  110. package/src/components/tools/registry/item-surface-move.test.ts +10 -2
  111. package/src/components/tools/registry/item-surface-move.ts +46 -24
  112. package/src/components/tools/registry/item-surface-resolver.test.ts +2 -2
  113. package/src/components/tools/registry/move-registry-node-tool.tsx +26 -10
  114. package/src/components/tools/select/box-select-tool.tsx +2 -12
  115. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  116. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  117. package/src/components/tools/select/select-candidates.test.ts +37 -4
  118. package/src/components/tools/select/select-candidates.ts +15 -11
  119. package/src/components/tools/shared/affordance-dispatch.test.ts +24 -0
  120. package/src/components/tools/shared/affordance-dispatch.ts +13 -0
  121. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  122. package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
  123. package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
  124. package/src/components/tools/shared/pointer-support-cap.ts +17 -5
  125. package/src/components/tools/shared/surface-rejection.test.tsx +5 -4
  126. package/src/components/tools/shared/surface-rejection.tsx +14 -0
  127. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  128. package/src/components/tools/stair/stair-defaults.ts +0 -3
  129. package/src/components/tools/stair/stair-tool.tsx +90 -42
  130. package/src/components/tools/tool-manager.tsx +82 -23
  131. package/src/components/tools/wall/wall-drafting.test.ts +19 -4
  132. package/src/components/tools/zone/zone-tool.tsx +4 -12
  133. package/src/components/ui/action-menu/action-button.tsx +3 -0
  134. package/src/components/ui/action-menu/camera-actions.tsx +2 -1
  135. package/src/components/ui/action-menu/control-modes.tsx +2 -35
  136. package/src/components/ui/action-menu/measurement-control.tsx +3 -10
  137. package/src/components/ui/build-panel/build-panel.tsx +235 -0
  138. package/src/components/ui/command-palette/editor-commands.tsx +4 -30
  139. package/src/components/ui/command-palette/index.tsx +5 -24
  140. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  141. package/src/components/ui/controls/material-paint-panel.tsx +101 -91
  142. package/src/components/ui/controls/material-picker.tsx +8 -67
  143. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  144. package/src/components/ui/controls/select-control.tsx +65 -0
  145. package/src/components/ui/controls/slider-control.tsx +76 -31
  146. package/src/components/ui/floating-level-selector.tsx +9 -14
  147. package/src/components/ui/helpers/building-helper.tsx +4 -1
  148. package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
  149. package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
  150. package/src/components/ui/helpers/helper-manager.tsx +348 -19
  151. package/src/components/ui/helpers/item-helper.tsx +8 -0
  152. package/src/components/ui/helpers/registered-tool-helper.tsx +10 -33
  153. package/src/components/ui/icon-ref.tsx +45 -1
  154. package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
  155. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  156. package/src/components/ui/node-icon-image.tsx +16 -0
  157. package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
  158. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  159. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  160. package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
  161. package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
  162. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  163. package/src/components/ui/panels/multi-field-value.ts +6 -3
  164. package/src/components/ui/panels/multi-height-mode.tsx +27 -21
  165. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  166. package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
  167. package/src/components/ui/panels/node-display.ts +8 -3
  168. package/src/components/ui/panels/panel-manager.tsx +103 -68
  169. package/src/components/ui/panels/panel-wrapper.tsx +78 -36
  170. package/src/components/ui/panels/parametric-field-control.tsx +1 -1
  171. package/src/components/ui/panels/parametric-inspector.tsx +7 -19
  172. package/src/components/ui/panels/reference-panel.tsx +2 -3
  173. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  174. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  175. package/src/components/ui/panels/room-name-options.ts +47 -0
  176. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  177. package/src/components/ui/panels/room-panel.tsx +725 -0
  178. package/src/components/ui/panels/shape-choice.tsx +58 -0
  179. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  180. package/src/components/ui/primitives/color-dot.tsx +36 -24
  181. package/src/components/ui/primitives/shortcut-token.tsx +4 -0
  182. package/src/components/ui/primitives/sidebar.tsx +2 -0
  183. package/src/components/ui/right-stack.test.ts +29 -0
  184. package/src/components/ui/right-stack.tsx +145 -0
  185. package/src/components/ui/sidebar/icon-rail.tsx +5 -0
  186. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  187. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +615 -372
  188. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +7 -7
  189. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  190. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  191. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  192. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  193. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  194. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  195. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  196. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  197. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  198. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  199. package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
  200. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  201. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  202. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  203. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  204. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  205. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  206. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  207. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  208. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  209. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  210. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
  211. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  212. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  213. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  214. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  215. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
  216. package/src/components/ui/sidebar/tab-bar.tsx +10 -0
  217. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  218. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  219. package/src/components/viewer/floorplan-preview-visibility.test.ts +35 -0
  220. package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
  221. package/src/components/viewer/floorplan-preview.tsx +6 -18
  222. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  223. package/src/components/viewer/viewer-rooms.tsx +175 -0
  224. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  225. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  226. package/src/components/viewer/viewer-stage.tsx +2 -2
  227. package/src/components/viewer-zone-system.tsx +9 -9
  228. package/src/hooks/use-auto-save.integration.test.tsx +311 -0
  229. package/src/hooks/use-auto-save.ts +55 -14
  230. package/src/hooks/use-ceiling-events.test.ts +1 -0
  231. package/src/hooks/use-ceiling-events.ts +2 -0
  232. package/src/hooks/use-grid-events.test.ts +2 -0
  233. package/src/hooks/use-grid-events.ts +16 -11
  234. package/src/hooks/use-keyboard.test.ts +182 -0
  235. package/src/hooks/use-keyboard.ts +122 -91
  236. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  237. package/src/hooks/use-open-wall-ends.ts +238 -0
  238. package/src/hooks/use-placement-notice.test.ts +48 -0
  239. package/src/hooks/use-placement-notice.ts +42 -0
  240. package/src/hooks/use-selected-room.ts +143 -0
  241. package/src/index.tsx +306 -39
  242. package/src/lib/catalog-panel-model.ts +39 -0
  243. package/src/lib/catalog-selection.test.ts +27 -0
  244. package/src/lib/catalog-selection.ts +8 -0
  245. package/src/lib/ceiling-edit-session.test.ts +321 -0
  246. package/src/lib/ceiling-edit-session.ts +206 -0
  247. package/src/lib/ceiling-region-session.test.ts +179 -0
  248. package/src/lib/contextual-help.test.ts +27 -1
  249. package/src/lib/contextual-help.ts +16 -9
  250. package/src/lib/continuation.test.ts +19 -1
  251. package/src/lib/continuation.ts +25 -4
  252. package/src/lib/door-interaction.ts +14 -0
  253. package/src/lib/editor-level-display.test.ts +96 -0
  254. package/src/lib/editor-level-display.ts +48 -0
  255. package/src/lib/editor-views.ts +144 -0
  256. package/src/lib/export-geometry-wait.test.ts +90 -0
  257. package/src/lib/floor-edit-session.ts +153 -0
  258. package/src/lib/floor-footprints.test.ts +563 -0
  259. package/src/lib/floor-footprints.ts +395 -0
  260. package/src/lib/floor-foundation-support.test.ts +283 -0
  261. package/src/lib/floor-opening-draft.ts +218 -0
  262. package/src/lib/floor-openings.test.ts +216 -0
  263. package/src/lib/floor-region-geometry.ts +191 -0
  264. package/src/lib/floor-region-session.test.ts +485 -0
  265. package/src/lib/floor-region-session.ts +589 -0
  266. package/src/lib/floor-region-snap.ts +101 -0
  267. package/src/lib/floor-step-hover.test.ts +32 -0
  268. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  269. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  270. package/src/lib/floorplan/dimension-drive.ts +479 -0
  271. package/src/lib/floorplan/floorplan-export.test.ts +15 -9
  272. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  273. package/src/lib/floorplan/floorplan-extension.ts +48 -0
  274. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  275. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  276. package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
  277. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
  278. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
  279. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  280. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  281. package/src/lib/floorplan/geometry.ts +13 -6
  282. package/src/lib/floorplan/index.ts +0 -9
  283. package/src/lib/floorplan/items.ts +5 -2
  284. package/src/lib/floorplan/marks.test.ts +213 -0
  285. package/src/lib/floorplan/marks.ts +238 -0
  286. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  287. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  288. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  289. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  290. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  291. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  292. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  293. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  294. package/src/lib/floorplan/schedules.test.ts +189 -0
  295. package/src/lib/floorplan/schedules.ts +339 -0
  296. package/src/lib/floorplan/sheet-export.test.ts +100 -0
  297. package/src/lib/floorplan/sheet-export.ts +318 -0
  298. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
  299. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  300. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  301. package/src/lib/floorplan/site-plan/coverage.ts +232 -0
  302. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  303. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  304. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  305. package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
  306. package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
  307. package/src/lib/floorplan/site-plan/geometry.ts +557 -0
  308. package/src/lib/floorplan/site-plan/index.ts +71 -0
  309. package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
  310. package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
  311. package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
  312. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  313. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  314. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  315. package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
  316. package/src/lib/floorplan/types.ts +1 -27
  317. package/src/lib/fresh-planar-placement.test.ts +36 -0
  318. package/src/lib/fresh-planar-placement.ts +191 -36
  319. package/src/lib/geometry-script/author.ts +152 -0
  320. package/src/lib/geometry-script/client.ts +152 -0
  321. package/src/lib/geometry-script/protocol.ts +12 -0
  322. package/src/lib/gesture-lifecycle.test.ts +595 -0
  323. package/src/lib/gesture-lifecycle.ts +290 -0
  324. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  325. package/src/lib/glb-export.test.ts +544 -16
  326. package/src/lib/glb-export.ts +256 -125
  327. package/src/lib/grid-frame.test.ts +19 -0
  328. package/src/lib/grid-frame.ts +18 -0
  329. package/src/lib/history.test.ts +6 -0
  330. package/src/lib/history.ts +7 -0
  331. package/src/lib/hud-title.ts +109 -0
  332. package/src/lib/hydration-gated-storage.ts +49 -0
  333. package/src/lib/ifc-export.test.ts +174 -0
  334. package/src/lib/ifc-export.ts +201 -0
  335. package/src/lib/ifc-parts.ts +15 -0
  336. package/src/lib/inspector-expanded.test.ts +44 -0
  337. package/src/lib/inspector-expanded.ts +36 -0
  338. package/src/lib/interaction/scope.ts +27 -1
  339. package/src/lib/keyboard-pan.test.ts +184 -0
  340. package/src/lib/keyboard-pan.ts +238 -0
  341. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  342. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  343. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  344. package/src/lib/lot/clean-ring.test.ts +186 -0
  345. package/src/lib/lot/clean-ring.ts +217 -0
  346. package/src/lib/lot/dossier.test.ts +202 -0
  347. package/src/lib/lot/dossier.ts +498 -0
  348. package/src/lib/lot/drop-in.test.ts +326 -0
  349. package/src/lib/lot/drop-in.ts +407 -0
  350. package/src/lib/lot/index.ts +43 -0
  351. package/src/lib/lot/lot-patch.test.ts +340 -0
  352. package/src/lib/lot/lot-patch.ts +394 -0
  353. package/src/lib/lot/parcel-provider.ts +43 -0
  354. package/src/lib/lot/terrain.test.ts +161 -0
  355. package/src/lib/lot/terrain.ts +266 -0
  356. package/src/lib/material-catalog-model.ts +74 -0
  357. package/src/lib/material-paint-panel-model.ts +80 -0
  358. package/src/lib/material-paint.ts +4 -0
  359. package/src/lib/mezzanine-actions.test.ts +342 -0
  360. package/src/lib/mezzanine-draft.test.ts +501 -0
  361. package/src/lib/mezzanine-draft.ts +278 -0
  362. package/src/lib/mezzanine-messages.ts +26 -0
  363. package/src/lib/mezzanine-selection.test.ts +207 -0
  364. package/src/lib/mezzanine-stairs.ts +46 -0
  365. package/src/lib/model-export.ts +3 -1
  366. package/src/lib/node-action-movement.ts +17 -6
  367. package/src/lib/node-display-label.ts +37 -0
  368. package/src/lib/orbit-floor-clearance.test.ts +165 -0
  369. package/src/lib/orbit-floor-clearance.ts +106 -0
  370. package/src/lib/overlay-transform.test.ts +28 -0
  371. package/src/lib/overlay-transform.ts +12 -0
  372. package/src/lib/paint-commit-route.test.ts +37 -0
  373. package/src/lib/paint-commit-route.ts +17 -0
  374. package/src/lib/paint-eyedropper.test.tsx +165 -0
  375. package/src/lib/paint-eyedropper.ts +99 -0
  376. package/src/lib/paint-pass-through.test.ts +111 -0
  377. package/src/lib/paint-pass-through.ts +76 -0
  378. package/src/lib/paint-region-hover.ts +25 -0
  379. package/src/lib/paint-region-mode.ts +154 -0
  380. package/src/lib/paint-regions.test.ts +166 -0
  381. package/src/lib/paint-regions.ts +192 -0
  382. package/src/lib/paint-scope.test.ts +317 -35
  383. package/src/lib/paint-scope.ts +266 -25
  384. package/src/lib/panel-rows.ts +96 -0
  385. package/src/lib/panel-tool-options.test.ts +34 -0
  386. package/src/lib/panel-tool-options.ts +46 -0
  387. package/src/lib/parametric-node-update.ts +37 -0
  388. package/src/lib/plate-paint-affected.test.ts +300 -0
  389. package/src/lib/plate-paint-affected.ts +177 -0
  390. package/src/lib/plugin-chat.ts +50 -0
  391. package/src/lib/plugin-panels.test.ts +25 -0
  392. package/src/lib/plugin-panels.ts +94 -0
  393. package/src/lib/print-shell-compiler.ts +1 -0
  394. package/src/lib/registered-draft-snap.ts +12 -0
  395. package/src/lib/roof-duplication.ts +10 -2
  396. package/src/lib/room-assembly-overlay.test.ts +275 -0
  397. package/src/lib/room-assembly-overlay.ts +266 -0
  398. package/src/lib/room-built-on.test.ts +379 -0
  399. package/src/lib/room-built-on.ts +138 -0
  400. package/src/lib/room-construction-commands.ts +712 -0
  401. package/src/lib/room-construction.test.ts +746 -0
  402. package/src/lib/room-construction.ts +385 -0
  403. package/src/lib/room-controls-phase6.test.ts +630 -0
  404. package/src/lib/room-divide-session.ts +523 -0
  405. package/src/lib/room-handle-drag.ts +1068 -0
  406. package/src/lib/room-name-catalog.test.ts +90 -0
  407. package/src/lib/room-name-catalog.ts +168 -0
  408. package/src/lib/room-push-dimensions.test.ts +311 -0
  409. package/src/lib/room-push-dimensions.ts +251 -0
  410. package/src/lib/room-selection-commands.ts +62 -0
  411. package/src/lib/room-selection.test.ts +973 -0
  412. package/src/lib/room-selection.ts +417 -0
  413. package/src/lib/room-structure-commands.ts +94 -0
  414. package/src/lib/room-structure.test.ts +921 -0
  415. package/src/lib/room-transform-session.ts +570 -0
  416. package/src/lib/room-zone-routing.test.ts +294 -0
  417. package/src/lib/room-zone-routing.ts +197 -0
  418. package/src/lib/scene-clipboard.test.ts +364 -0
  419. package/src/lib/scene-clipboard.ts +229 -48
  420. package/src/lib/scene-material-refs.test.ts +79 -0
  421. package/src/lib/scene-material-refs.ts +79 -0
  422. package/src/lib/scene.test.ts +87 -0
  423. package/src/lib/scene.ts +39 -45
  424. package/src/lib/selection-routing.test.ts +129 -45
  425. package/src/lib/selection-routing.ts +53 -74
  426. package/src/lib/session-writes.test.ts +120 -0
  427. package/src/lib/session-writes.ts +205 -0
  428. package/src/lib/sidebar-panel.ts +62 -0
  429. package/src/lib/site-boundary.test.ts +4 -6
  430. package/src/lib/site-boundary.ts +1 -1
  431. package/src/lib/snapping-mode.test.ts +17 -2
  432. package/src/lib/snapping-mode.ts +19 -7
  433. package/src/lib/spatial-drag-plane.test.ts +53 -0
  434. package/src/lib/spatial-drag-plane.ts +19 -0
  435. package/src/lib/spatial-pointer-input.test.ts +72 -0
  436. package/src/lib/spatial-pointer-input.ts +62 -0
  437. package/src/lib/spatial-snap.test.ts +47 -0
  438. package/src/lib/terrace-draft.test.ts +145 -0
  439. package/src/lib/terrace-draft.ts +213 -0
  440. package/src/lib/terrain-panel-model.ts +117 -0
  441. package/src/lib/tool-cursor-icon.test.ts +16 -0
  442. package/src/lib/tool-cursor-icon.ts +11 -0
  443. package/src/lib/typing-target.ts +9 -0
  444. package/src/lib/units.ts +77 -39
  445. package/src/lib/vector-edges.test.ts +58 -0
  446. package/src/lib/vector-edges.ts +400 -0
  447. package/src/lib/viewer-selection.test.ts +577 -0
  448. package/src/lib/viewer-selection.ts +569 -0
  449. package/src/lib/wall-draw-variant.test.ts +71 -0
  450. package/src/lib/wall-draw-variant.ts +91 -0
  451. package/src/lib/wall-paint-region-session.ts +246 -0
  452. package/src/lib/wall-paint-region.test.ts +373 -0
  453. package/src/lib/wall-polygon-draft.test.ts +262 -0
  454. package/src/lib/wall-polygon-draft.ts +286 -0
  455. package/src/lib/wall-push.test.ts +453 -0
  456. package/src/lib/wall-region-face.ts +352 -0
  457. package/src/lib/wall-region-handles.test.ts +304 -0
  458. package/src/lib/wall-region-handles.ts +368 -0
  459. package/src/lib/wall-region-list.test.ts +119 -0
  460. package/src/lib/wall-region-list.ts +136 -0
  461. package/src/lib/wall-region-snap.ts +110 -0
  462. package/src/lib/window-interaction.ts +14 -0
  463. package/src/lib/world-grid-snap.test.ts +48 -0
  464. package/src/lib/world-grid-snap.ts +9 -5
  465. package/src/lib/zone-content.ts +4 -1
  466. package/src/lib/zone-name.test.ts +17 -0
  467. package/src/lib/zone-name.ts +17 -0
  468. package/src/store/opens-in-select-mode.test.ts +223 -0
  469. package/src/store/paint-mode.test.ts +188 -0
  470. package/src/store/rotation-snapping.test.ts +53 -0
  471. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  472. package/src/store/tool-mode.test.ts +137 -6
  473. package/src/store/use-delete-confirmation.ts +28 -1
  474. package/src/store/use-drawing-view.test.ts +24 -3
  475. package/src/store/use-drawing-view.ts +29 -1
  476. package/src/store/use-editor.tsx +442 -323
  477. package/src/store/use-floorplan-draft-preview.ts +23 -1
  478. package/src/store/use-floorplan-marquee.ts +12 -2
  479. package/src/store/use-interaction-scope.ts +155 -6
  480. package/src/store/use-quick-measurement-hud.ts +3 -3
  481. package/src/store/use-stair-build-preview.ts +13 -5
  482. package/src/store/use-wall-move-ghosts.ts +4 -2
  483. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  484. package/src/store/use-wall-region-handles-selection.ts +139 -0
  485. package/src/store/view-layout.test.ts +175 -0
  486. package/src/store/view-layout.ts +196 -0
  487. package/src/test-utils/immediate-animation-frames.ts +16 -0
  488. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  489. package/src/test-utils/selection-harness.ts +82 -0
  490. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  491. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  492. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  493. package/src/components/tools/fence/fence-drafting.ts +0 -266
  494. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  495. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  496. package/src/lib/floorplan/stairs.ts +0 -489
  497. package/src/lib/level-duplication.test.ts +0 -144
  498. package/src/lib/level-duplication.ts +0 -195
@@ -3,7 +3,7 @@ import { markPerfAction, useViewer } from '@pascal-app/viewer'
3
3
  import { Layers } from 'lucide-react'
4
4
  import { memo, useCallback, useState } from 'react'
5
5
  import { useShallow } from 'zustand/react/shallow'
6
- import { getDefaultLevelName } from '@pascal-app/core'
6
+ import { getDefaultLevelName, getLevelDisplayName } from '@pascal-app/core'
7
7
  import { InlineRenameInput } from './inline-rename-input'
8
8
  import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
9
9
  import { TreeNodeActions } from './tree-node-actions'
@@ -26,6 +26,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
26
26
  useShallow((s) => (s.nodes[nodeId] as LevelNode | undefined)?.children ?? []),
27
27
  )
28
28
  const level = useScene((s) => (s.nodes[nodeId] as LevelNode | undefined)?.level ?? 0)
29
+ const levelName = useScene((s) => (s.nodes[nodeId] as LevelNode | undefined)?.name)
29
30
  const isSelected = useViewer((state) => state.selection.levelId === nodeId)
30
31
  const isHovered = useViewer((state) => state.hoveredId === nodeId)
31
32
  const setSelection = useViewer((state) => state.setSelection)
@@ -40,6 +41,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
40
41
  const handleStopEditing = useCallback(() => setIsEditing(false), [])
41
42
 
42
43
  const defaultName = getDefaultLevelName(level)
44
+ const displayName = getLevelDisplayName({ name: levelName, level })
43
45
 
44
46
  return (
45
47
  <TreeNodeWrapper
@@ -55,6 +57,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
55
57
  label={
56
58
  <InlineRenameInput
57
59
  defaultName={defaultName}
60
+ displayName={displayName}
58
61
  isEditing={isEditing}
59
62
  nodeId={nodeId}
60
63
  onStartEditing={handleStartEditing}
@@ -10,7 +10,6 @@ import { InlineRenameInput } from './inline-rename-input'
10
10
  import {
11
11
  focusTreeNode,
12
12
  handleTreeSelection,
13
- routeTreeSelectionToNode,
14
13
  TreeNode,
15
14
  TreeNodeWrapper,
16
15
  } from './tree-node'
@@ -99,9 +98,8 @@ export const RegistryTreeNode = memo(function RegistryTreeNode({
99
98
  useViewer.getState().selection.selectedIds,
100
99
  setSelection,
101
100
  )
102
- routeTreeSelectionToNode(node)
103
101
  },
104
- [node, nodeId, setSelection],
102
+ [nodeId, setSelection],
105
103
  )
106
104
 
107
105
  return (
@@ -49,15 +49,12 @@ export const RoofTreeNode = memo(function RoofTreeNode({
49
49
  const handleClick = useCallback(
50
50
  (e: React.MouseEvent) => {
51
51
  e.stopPropagation()
52
- const handled = handleTreeSelection(
52
+ handleTreeSelection(
53
53
  e,
54
54
  nodeId,
55
55
  useViewer.getState().selection.selectedIds,
56
56
  setSelection,
57
57
  )
58
- if (!handled && useEditor.getState().phase === 'furnish') {
59
- useEditor.getState().setPhase('structure')
60
- }
61
58
  if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
62
59
  setRoofHostDragArmedId(nodeId)
63
60
  }
@@ -9,7 +9,6 @@ import { InlineRenameInput } from './inline-rename-input'
9
9
  import {
10
10
  focusTreeNode,
11
11
  handleTreeSelection,
12
- routeTreeSelectionToNode,
13
12
  TreeNode,
14
13
  TreeNodeWrapper,
15
14
  } from './tree-node'
@@ -70,9 +69,8 @@ export const ShelfTreeNode = memo(function ShelfTreeNode({
70
69
  (e: React.MouseEvent) => {
71
70
  e.stopPropagation()
72
71
  handleTreeSelection(e, nodeId, useViewer.getState().selection.selectedIds, setSelection)
73
- routeTreeSelectionToNode(node)
74
72
  },
75
- [node, nodeId, setSelection],
73
+ [nodeId, setSelection],
76
74
  )
77
75
 
78
76
  const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
@@ -1,9 +1,8 @@
1
- import { type AnyNodeId, type SlabNode, useScene } from '@pascal-app/core'
1
+ import { type AnyNodeId, floorFootprintName, type SlabNode, useScene } from '@pascal-app/core'
2
2
  import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useState } from 'react'
5
5
  import { formatAreaLabel } from './../../../../../lib/measurements'
6
- import useEditor from './../../../../../store/use-editor'
7
6
  import { InlineRenameInput } from './inline-rename-input'
8
7
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
9
8
  import { TreeNodeActions } from './tree-node-actions'
@@ -27,19 +26,22 @@ export const SlabTreeNode = memo(function SlabTreeNode({
27
26
  const setSelection = useViewer((state) => state.setSelection)
28
27
  const setHoveredId = useViewer((state) => state.setHoveredId)
29
28
  const unit = useViewer((state) => state.unit)
29
+ // A base plate is one footprint's "Floor & foundation": listed by the
30
+ // footprint's name ("Shared floor", "Lanai floor"), opening that panel.
31
+ const footprint = useScene((s) => {
32
+ const node = s.nodes[nodeId]
33
+ return node?.type === 'slab' && node.plateRole === 'base' ? floorFootprintName(s.nodes, node) : null
34
+ })
30
35
 
31
36
  const handleClick = useCallback(
32
37
  (e: React.MouseEvent) => {
33
38
  e.stopPropagation()
34
- const handled = handleTreeSelection(
39
+ handleTreeSelection(
35
40
  e,
36
41
  nodeId,
37
42
  useViewer.getState().selection.selectedIds,
38
43
  setSelection,
39
44
  )
40
- if (!handled && useEditor.getState().phase === 'furnish') {
41
- useEditor.getState().setPhase('structure')
42
- }
43
45
  },
44
46
  [nodeId, setSelection],
45
47
  )
@@ -63,13 +65,19 @@ export const SlabTreeNode = memo(function SlabTreeNode({
63
65
  isSelected={isSelected}
64
66
  isVisible={isVisible}
65
67
  label={
66
- <InlineRenameInput
67
- defaultName={defaultName}
68
- isEditing={isEditing}
69
- nodeId={nodeId}
70
- onStartEditing={handleStartEditing}
71
- onStopEditing={handleStopEditing}
72
- />
68
+ footprint ? (
69
+ <span className="truncate" data-footprint-entry={nodeId}>
70
+ {footprint}
71
+ </span>
72
+ ) : (
73
+ <InlineRenameInput
74
+ defaultName={defaultName}
75
+ isEditing={isEditing}
76
+ nodeId={nodeId}
77
+ onStartEditing={handleStartEditing}
78
+ onStopEditing={handleStopEditing}
79
+ />
80
+ )
73
81
  }
74
82
  nodeId={nodeId}
75
83
  onClick={handleClick}
@@ -3,7 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useState } from 'react'
5
5
  import { SnapTargetIcon } from '../../../snap-target-badge'
6
- import useEditor from './../../../../../store/use-editor'
7
6
  import { InlineRenameInput } from './inline-rename-input'
8
7
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
9
8
  import { TreeNodeActions } from './tree-node-actions'
@@ -30,15 +29,12 @@ export const SolarPanelTreeNode = memo(function SolarPanelTreeNode({
30
29
  const handleClick = useCallback(
31
30
  (e: React.MouseEvent) => {
32
31
  e.stopPropagation()
33
- const handled = handleTreeSelection(
32
+ handleTreeSelection(
34
33
  e,
35
34
  nodeId,
36
35
  useViewer.getState().selection.selectedIds,
37
36
  setSelection,
38
37
  )
39
- if (!handled && useEditor.getState().phase === 'furnish') {
40
- useEditor.getState().setPhase('structure')
41
- }
42
38
  },
43
39
  [nodeId, setSelection],
44
40
  )
@@ -4,7 +4,6 @@ import { type SpawnNode, useScene } from '@pascal-app/core'
4
4
  import { useViewer } from '@pascal-app/viewer'
5
5
  import Image from 'next/image'
6
6
  import { memo, useCallback, useState } from 'react'
7
- import useEditor from './../../../../../store/use-editor'
8
7
  import { InlineRenameInput } from './inline-rename-input'
9
8
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
10
9
  import { TreeNodeActions } from './tree-node-actions'
@@ -30,15 +29,12 @@ export const SpawnTreeNode = memo(function SpawnTreeNode({
30
29
  const handleClick = useCallback(
31
30
  (e: React.MouseEvent) => {
32
31
  e.stopPropagation()
33
- const handled = handleTreeSelection(
32
+ handleTreeSelection(
34
33
  e,
35
34
  nodeId,
36
35
  useViewer.getState().selection.selectedIds,
37
36
  setSelection,
38
37
  )
39
- if (!handled && useEditor.getState().phase === 'furnish') {
40
- useEditor.getState().setPhase('structure')
41
- }
42
38
  },
43
39
  [nodeId, setSelection],
44
40
  )
@@ -4,7 +4,6 @@ import { AnimatePresence } from 'motion/react'
4
4
  import Image from 'next/image'
5
5
  import { memo, useCallback, useEffect, useState } from 'react'
6
6
  import { useShallow } from 'zustand/react/shallow'
7
- import useEditor from '../../../../../store/use-editor'
8
7
  import { InlineRenameInput } from './inline-rename-input'
9
8
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
10
9
  import { TreeNodeActions } from './tree-node-actions'
@@ -48,15 +47,12 @@ export const StairTreeNode = memo(function StairTreeNode({
48
47
  const handleClick = useCallback(
49
48
  (e: React.MouseEvent) => {
50
49
  e.stopPropagation()
51
- const handled = handleTreeSelection(
50
+ handleTreeSelection(
52
51
  e,
53
52
  nodeId,
54
53
  useViewer.getState().selection.selectedIds,
55
54
  setSelection,
56
55
  )
57
- if (!handled && useEditor.getState().phase === 'furnish') {
58
- useEditor.getState().setPhase('structure')
59
- }
60
56
  },
61
57
  [nodeId, setSelection],
62
58
  )
@@ -3,8 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import { ChevronRight } from 'lucide-react'
4
4
  import { AnimatePresence, motion } from 'motion/react'
5
5
  import { forwardRef, memo, useEffect, useRef } from 'react'
6
- import { resolveNodeSelectionTarget } from '../../../../../lib/selection-routing'
7
- import useEditor from '../../../../../store/use-editor'
8
6
  import { expandSessionSelectionForNode } from '../../../../../store/use-session-groups'
9
7
 
10
8
  export function handleTreeSelection(
@@ -63,31 +61,6 @@ export function focusTreeNode(nodeId: AnyNodeId) {
63
61
  emitter.emit('camera-controls:focus', { nodeId })
64
62
  }
65
63
 
66
- export function routeTreeSelectionToNode(node: AnyNode | null | undefined) {
67
- const target = node ? resolveNodeSelectionTarget(node) : null
68
- if (!target) return
69
-
70
- const selectedIdsAfterClick = useViewer.getState().selection.selectedIds
71
- const editor = useEditor.getState()
72
- let didRoute = false
73
-
74
- if (target.phase !== editor.phase) {
75
- editor.setPhase(target.phase)
76
- didRoute = true
77
- }
78
- if (
79
- target.phase === 'structure' &&
80
- target.structureLayer &&
81
- target.structureLayer !== useEditor.getState().structureLayer
82
- ) {
83
- useEditor.getState().setStructureLayer(target.structureLayer)
84
- didRoute = true
85
- }
86
- if (didRoute) {
87
- useViewer.getState().setSelection({ selectedIds: selectedIdsAfterClick })
88
- }
89
- }
90
-
91
64
  import { cn } from '../../../../../lib/utils'
92
65
  import { BuildingTreeNode } from './building-tree-node'
93
66
  import { CeilingTreeNode } from './ceiling-tree-node'
@@ -14,7 +14,7 @@ import { useShallow } from 'zustand/react/shallow'
14
14
  import { ColorDot } from './../../../../../components/ui/primitives/color-dot'
15
15
  import { assignZoneToUnit, enterUnitFocus } from './../../../../../lib/units'
16
16
  import { InlineRenameInput } from './inline-rename-input'
17
- import { routeTreeSelectionToNode, TreeNodeWrapper } from './tree-node'
17
+ import { TreeNodeWrapper } from './tree-node'
18
18
 
19
19
  type UnitWarningCode = ReturnType<typeof unitWarnings>[number]['code']
20
20
 
@@ -55,7 +55,6 @@ export const UnitZoneRow = memo(function UnitZoneRow({
55
55
  event.stopPropagation()
56
56
  if (!zone) return
57
57
  setSelection({ levelId: zone.parentId as LevelNode['id'], zoneId })
58
- routeTreeSelectionToNode(zone)
59
58
  },
60
59
  [zone, zoneId, setSelection],
61
60
  )
@@ -94,7 +93,7 @@ export const UnitZoneRow = memo(function UnitZoneRow({
94
93
  keepIconColor
95
94
  label={
96
95
  <span className="flex min-w-0 items-baseline gap-1.5">
97
- <span className="truncate">{zone.name || 'Zone'}</span>
96
+ <span className="truncate">{zone.name || (zone.spaceRole === 'room' ? 'Room' : 'Zone')}</span>
98
97
  {levelLabel && (
99
98
  <span className="shrink-0 text-muted-foreground text-xs">{levelLabel}</span>
100
99
  )}
@@ -3,7 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useEffect, useRef, useState } from 'react'
5
5
  import { useShallow } from 'zustand/react/shallow'
6
- import useEditor from './../../../../../store/use-editor'
7
6
  import { InlineRenameInput } from './inline-rename-input'
8
7
  import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
9
8
  import { TreeNodeActions } from './tree-node-actions'
@@ -52,15 +51,12 @@ export const WallTreeNode = memo(function WallTreeNode({
52
51
  const handleClick = useCallback(
53
52
  (e: React.MouseEvent) => {
54
53
  e.stopPropagation()
55
- const handled = handleTreeSelection(
54
+ handleTreeSelection(
56
55
  e,
57
56
  nodeId,
58
57
  useViewer.getState().selection.selectedIds,
59
58
  setSelection,
60
59
  )
61
- if (!handled && useEditor.getState().phase === 'furnish') {
62
- useEditor.getState().setPhase('structure')
63
- }
64
60
  },
65
61
  [nodeId, setSelection],
66
62
  )
@@ -5,7 +5,6 @@ import { useViewer } from '@pascal-app/viewer'
5
5
  import Image from 'next/image'
6
6
  import { memo, useCallback, useState } from 'react'
7
7
  import { resolveNodeSnapTarget, SnapTargetIcon } from '../../../snap-target-badge'
8
- import useEditor from './../../../../../store/use-editor'
9
8
  import { InlineRenameInput } from './inline-rename-input'
10
9
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
11
10
  import { TreeNodeActions } from './tree-node-actions'
@@ -32,15 +31,12 @@ export const WindowTreeNode = memo(function WindowTreeNode({
32
31
  const handleClick = useCallback(
33
32
  (e: React.MouseEvent) => {
34
33
  e.stopPropagation()
35
- const handled = handleTreeSelection(
34
+ handleTreeSelection(
36
35
  e,
37
36
  nodeId,
38
37
  useViewer.getState().selection.selectedIds,
39
38
  setSelection,
40
39
  )
41
- if (!handled && useEditor.getState().phase === 'furnish') {
42
- useEditor.getState().setPhase('structure')
43
- }
44
40
  },
45
41
  [nodeId, setSelection],
46
42
  )
@@ -3,6 +3,11 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import { memo, useCallback, useState } from 'react'
4
4
  import { ColorDot } from './../../../../../components/ui/primitives/color-dot'
5
5
  import { formatAreaLabel } from './../../../../../lib/measurements'
6
+ import {
7
+ selectZoneOrRoom,
8
+ useZoneSelected,
9
+ zoneKindLabel,
10
+ } from './../../../../../lib/room-zone-routing'
6
11
  import { toggleZoneMembership } from './../../../../../lib/units'
7
12
  import { InlineRenameInput } from './inline-rename-input'
8
13
  import { focusTreeNode, TreeNodeWrapper } from './tree-node'
@@ -25,9 +30,9 @@ export const ZoneTreeNode = memo(function ZoneTreeNode({
25
30
  const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
26
31
  const color = useScene((s) => (s.nodes[nodeId] as ZoneNode | undefined)?.color)
27
32
  const polygon = useScene((s) => (s.nodes[nodeId] as ZoneNode | undefined)?.polygon ?? [])
28
- const isSelected = useViewer((state) => state.selection.zoneId === nodeId)
33
+ const isSelected = useZoneSelected(nodeId)
34
+ const kind = useScene((s) => zoneKindLabel(s.nodes[nodeId] as ZoneNode | undefined))
29
35
  const isHovered = useViewer((state) => state.hoveredId === nodeId)
30
- const setSelection = useViewer((state) => state.setSelection)
31
36
  const setHoveredId = useViewer((state) => state.setHoveredId)
32
37
  const unit = useViewer((state) => state.unit)
33
38
  const focusedUnitId = useViewer((state) => state.focusedUnitId)
@@ -36,14 +41,14 @@ export const ZoneTreeNode = memo(function ZoneTreeNode({
36
41
  return focused?.type === 'unit' ? focused : null
37
42
  })
38
43
 
39
- const handleClick = useCallback(() => setSelection({ zoneId: nodeId }), [nodeId, setSelection])
44
+ const handleClick = useCallback(() => selectZoneOrRoom(nodeId), [nodeId])
40
45
  const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
41
46
  const handleMouseEnter = useCallback(() => setHoveredId(nodeId), [nodeId, setHoveredId])
42
47
  const handleMouseLeave = useCallback(() => setHoveredId(null), [setHoveredId])
43
48
  const handleStartEditing = useCallback(() => setIsEditing(true), [])
44
49
  const handleStopEditing = useCallback(() => setIsEditing(false), [])
45
50
 
46
- const defaultName = `Zone (${formatAreaLabel(calculatePolygonArea(polygon), unit)})`
51
+ const defaultName = `${kind} (${formatAreaLabel(calculatePolygonArea(polygon), unit)})`
47
52
 
48
53
  return (
49
54
  <TreeNodeWrapper
@@ -137,7 +137,6 @@ export function ZonePanel() {
137
137
  const currentLevelId = useViewer((state) => state.selection.levelId)
138
138
  const selectedZoneId = useViewer((state) => state.selection.zoneId)
139
139
  const setSelection = useViewer((state) => state.setSelection)
140
- const setPhase = useEditor((state) => state.setPhase)
141
140
  const setMode = useEditor((state) => state.setMode)
142
141
  const setTool = useEditor((state) => state.setTool)
143
142
 
@@ -149,7 +148,6 @@ export function ZonePanel() {
149
148
 
150
149
  const handleAddZone = () => {
151
150
  if (currentLevelId) {
152
- setPhase('structure')
153
151
  setMode('build')
154
152
  setTool('zone')
155
153
  }
@@ -19,6 +19,16 @@ export type SidebarTab = {
19
19
  * keeps the icon highlighted regardless of collapse.
20
20
  */
21
21
  noPanel?: boolean
22
+ /** Runs when the entry is picked — with `noPanel`, to put its surface on the stage. */
23
+ onSelect?: () => void
24
+ /**
25
+ * For a `noPanel` entry whose surface can leave the stage on its own (a view
26
+ * closed from the view bar): false once it has, and the rail goes back to
27
+ * the panel that was open before it.
28
+ */
29
+ stageActive?: boolean
30
+ /** Runs when the rail moves from this entry to another: a stage entry takes its surface down. */
31
+ onDeselect?: () => void
22
32
  }
23
33
 
24
34
  interface TabBarProps {
@@ -12,28 +12,37 @@ import {
12
12
  useSyncExternalStore,
13
13
  } from 'react'
14
14
  import useEditor from '../../../store/use-editor'
15
+ import { isViewVisible } from '../../../store/view-layout'
16
+ import { PluginsView } from '../../editor/views/plugins-view'
17
+ import { PLUGINS_VIEW_ID } from '../../editor/views/use-editor-views'
15
18
  import {
16
19
  editorHostPanelRegistry,
17
20
  type EditorHostPanel,
18
21
  managedPluginIds,
19
22
  showsPluginManager,
20
23
  } from '../../../lib/plugin-panels'
24
+ import { IconRefImage } from '../icon-ref'
21
25
  import { ErrorBoundary } from '../primitives/error-boundary'
22
26
  import type { ExtraPanel } from './icon-rail'
23
- import { PluginsPanel } from './panels/plugins-panel'
24
27
 
28
+ // The rail's "+" puts the plugin directory on the stage, as a view in the
29
+ // focused pane, and stays lit while it is there instead of leaving an
30
+ // unrelated panel open beside it. `component` serves the v1 sidebar.
25
31
  const pluginsManagerPanel: ExtraPanel = {
26
32
  id: 'plugins',
27
33
  label: 'Plugins',
28
- icon: <Plus className="h-5 w-5" />,
29
- component: PluginsPanel,
34
+ icon: <img alt="" className="size-8 object-contain" src="/icons/plugins.webp" />,
35
+ component: PluginsView,
36
+ noPanel: true,
37
+ onSelect: () => useEditor.getState().showView(PLUGINS_VIEW_ID),
38
+ onDeselect: () => useEditor.getState().closeView(PLUGINS_VIEW_ID),
30
39
  }
31
40
 
32
41
  /** Resolve a plugin's {@link IconRef} into a rail-sized React node. Mirrors the
33
42
  * inspector's `renderIcon`, sized for the 24px icon-rail button. */
34
43
  function renderIconRef(ref: IconRef): ReactNode {
35
44
  if (ref.kind === 'url') {
36
- return <img alt="" className="h-5 w-5 object-contain" src={ref.src} />
45
+ return <IconRefImage className="h-5 w-5" src={ref.src} />
37
46
  }
38
47
  if (ref.kind === 'iconify') {
39
48
  return <Icon height={20} icon={ref.name} width={20} />
@@ -106,6 +115,7 @@ export function useHostPanels(hostPanels?: ExtraPanel[]): ExtraPanel[] {
106
115
  const workspaceMode = useEditor((s) => s.workspaceMode)
107
116
  const installedPlugins = useScene((s) => s.installedPlugins)
108
117
  const readOnly = useScene((s) => s.readOnly)
118
+ const pluginsViewShown = useEditor((s) => isViewVisible(s, PLUGINS_VIEW_ID))
109
119
  const hostIds = new Set(hostPanels?.map((p) => p.id))
110
120
 
111
121
  useEffect(() => {
@@ -141,7 +151,7 @@ export function useHostPanels(hostPanels?: ExtraPanel[]): ExtraPanel[] {
141
151
  readOnly,
142
152
  workspaceMode,
143
153
  })
144
- ? [pluginsManagerPanel]
154
+ ? [{ ...pluginsManagerPanel, stageActive: pluginsViewShown }]
145
155
  : []
146
156
  return [...(hostPanels ?? []), ...fromRegistry, ...manager]
147
157
  }
@@ -1,25 +1,33 @@
1
1
  'use client'
2
2
 
3
3
  import type React from 'react'
4
+ import { cn } from '../../lib/utils'
4
5
  import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/primitives/tooltip'
5
6
 
6
7
  export type FloorplanCompassButtonProps = {
7
8
  northRotationDeg: number
8
9
  onAlignNorth: () => void
9
10
  needleRef?: React.RefObject<SVGSVGElement | null>
11
+ /** Horizontal anchor; defaults to the bottom-left corner. */
12
+ style?: React.CSSProperties
10
13
  }
11
14
 
12
15
  export function FloorplanCompassButton({
13
16
  northRotationDeg,
14
17
  onAlignNorth,
15
18
  needleRef,
19
+ style,
16
20
  }: FloorplanCompassButtonProps) {
17
21
  return (
18
22
  <Tooltip>
19
23
  <TooltipTrigger asChild>
20
24
  <button
21
25
  aria-label="Align view to north"
22
- className="group pointer-events-auto absolute bottom-3 left-3 z-30 flex h-8 w-8 items-center justify-center rounded-full border border-black/10 bg-white/85 shadow-sm backdrop-blur-md transition hover:bg-white hover:shadow-md dark:border-white/10 dark:bg-neutral-900/85 dark:hover:bg-neutral-900"
26
+ className={cn(
27
+ 'group pointer-events-auto absolute bottom-3 z-30 flex h-8 w-8 items-center justify-center rounded-full border border-black/10 bg-white/85 shadow-sm backdrop-blur-md transition hover:bg-white hover:shadow-md dark:border-white/10 dark:bg-neutral-900/85 dark:hover:bg-neutral-900',
28
+ !style && 'left-3',
29
+ )}
30
+ style={style}
23
31
  onClick={(event) => {
24
32
  event.preventDefault()
25
33
  event.stopPropagation()
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import type { AnyNode } from '@pascal-app/core'
3
+ import { isVisibleInFloorplan } from './floorplan-preview-visibility'
4
+
5
+ const node = (id: string, type: string, parentId: string | null, visible = true) =>
6
+ ({ object: 'node', id, type, parentId, visible, metadata: {} }) as unknown as AnyNode
7
+
8
+ describe('isVisibleInFloorplan', () => {
9
+ test('a hidden Site keeps the buildings on it in the plan', () => {
10
+ const nodes: Record<string, AnyNode> = {
11
+ site_a: node('site_a', 'site', null, false),
12
+ building_a: node('building_a', 'building', 'site_a'),
13
+ level_a: node('level_a', 'level', 'building_a'),
14
+ wall_a: node('wall_a', 'wall', 'level_a'),
15
+ wall_b: node('wall_b', 'wall', 'level_a', false),
16
+ }
17
+ expect(isVisibleInFloorplan(nodes.wall_a!, nodes)).toBe(true)
18
+ expect(isVisibleInFloorplan(nodes.wall_b!, nodes)).toBe(false)
19
+ expect(isVisibleInFloorplan(nodes.site_a!, nodes)).toBe(false)
20
+ })
21
+
22
+ test('a hidden building or level still hides its subtree', () => {
23
+ const nodes: Record<string, AnyNode> = {
24
+ site_a: node('site_a', 'site', null),
25
+ building_a: node('building_a', 'building', 'site_a', false),
26
+ level_a: node('level_a', 'level', 'building_a'),
27
+ wall_a: node('wall_a', 'wall', 'level_a'),
28
+ building_b: node('building_b', 'building', 'site_a'),
29
+ level_b: node('level_b', 'level', 'building_b', false),
30
+ wall_b: node('wall_b', 'wall', 'level_b'),
31
+ }
32
+ expect(isVisibleInFloorplan(nodes.wall_a!, nodes)).toBe(false)
33
+ expect(isVisibleInFloorplan(nodes.wall_b!, nodes)).toBe(false)
34
+ })
35
+ })
@@ -0,0 +1,19 @@
1
+ import { type AnyNode, hidesDescendants } from '@pascal-app/core'
2
+
3
+ /**
4
+ * A node draws in the plan unless it, or an ancestor whose flag reaches its
5
+ * descendants, is hidden. A hidden Site keeps its buildings on the plan; see
6
+ * `hidesDescendants`.
7
+ */
8
+ export function isVisibleInFloorplan(node: AnyNode, nodes: Record<string, AnyNode>): boolean {
9
+ if (node.visible === false) return false
10
+ const seen = new Set<string>([node.id])
11
+ let current: AnyNode | undefined = node.parentId ? nodes[node.parentId] : undefined
12
+ while (current) {
13
+ if (seen.has(current.id)) return true
14
+ seen.add(current.id)
15
+ if (current.visible === false && hidesDescendants(current)) return false
16
+ current = current.parentId ? nodes[current.parentId] : undefined
17
+ }
18
+ return true
19
+ }
@@ -6,6 +6,7 @@ import {
6
6
  type FloorplanGeometry,
7
7
  type FloorplanPalette,
8
8
  type GeometryContext,
9
+ getLevelDisplayName,
9
10
  isNodeKindEnabled,
10
11
  nodeRegistry,
11
12
  useScene,
@@ -62,6 +63,7 @@ import {
62
63
  rotateFloorplanPoint,
63
64
  visibleFloorplanViewWidth,
64
65
  } from './floorplan-preview-navigation'
66
+ import { isVisibleInFloorplan } from './floorplan-preview-visibility'
65
67
 
66
68
  const READ_ONLY_PALETTE: FloorplanPalette = {
67
69
  selectedStroke: '#4f46e5',
@@ -142,12 +144,10 @@ function boundsToViewBox(bounds: FloorplanBounds): FloorplanViewBox {
142
144
  }
143
145
 
144
146
  function levelLabel(level: AnyNode): string {
145
- const named = (level as { name?: string }).name?.trim()
146
- if (named) return named
147
- const ordinal = (level as { level?: number }).level ?? 0
148
- if (ordinal === 0) return 'Ground floor'
149
- if (ordinal < 0) return `Basement ${Math.abs(ordinal)}`
150
- return `Level ${ordinal}`
147
+ return getLevelDisplayName({
148
+ name: (level as { name?: string }).name,
149
+ level: (level as { level?: number }).level ?? 0,
150
+ })
151
151
  }
152
152
 
153
153
  function collectLevelTree(root: AnyNode, nodes: Record<string, AnyNode>): AnyNode[] {
@@ -193,18 +193,6 @@ export function normalizeFloorplanPreviewNodes(
193
193
  return normalized
194
194
  }
195
195
 
196
- function isVisibleInFloorplan(node: AnyNode, nodes: Record<string, AnyNode>): boolean {
197
- const seen = new Set<string>()
198
- let current: AnyNode | undefined = node
199
- while (current) {
200
- if (seen.has(current.id)) return true
201
- seen.add(current.id)
202
- if (current.visible === false) return false
203
- current = current.parentId ? nodes[current.parentId] : undefined
204
- }
205
- return true
206
- }
207
-
208
196
  function buildFloorplanGeometries(
209
197
  nodes: Record<string, AnyNode>,
210
198
  installedPlugins: readonly string[] | undefined,