@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
@@ -0,0 +1,235 @@
1
+ 'use client'
2
+
3
+ import { ChevronRight } from 'lucide-react'
4
+ import { type ComponentPropsWithoutRef, type ReactNode, useEffect, useState } from 'react'
5
+ import { cn } from '../../../lib/utils'
6
+ import {
7
+ WALL_DRAW_IDLE_CAPTION,
8
+ WALL_DRAW_VARIANTS,
9
+ type WallDrawVariant,
10
+ } from '../../../lib/wall-draw-variant'
11
+
12
+ /**
13
+ * The Build panel's grouped layout (Rooms / Add to rooms / Outdoor / Advanced),
14
+ * shared by the community and standalone editor Build tabs so both read the
15
+ * same. Hosts own what each tile arms; these only lay the groups out.
16
+ */
17
+ export function BuildPanelSection({
18
+ children,
19
+ hint,
20
+ id,
21
+ title,
22
+ }: {
23
+ children: ReactNode
24
+ hint?: string
25
+ id: string
26
+ title: string
27
+ }) {
28
+ return (
29
+ <section className="flex flex-col" data-build-group={id}>
30
+ <div className="mb-2 flex items-baseline justify-between gap-2 px-0.5">
31
+ <h3 className="font-medium text-foreground text-xs">{title}</h3>
32
+ {hint ? <span className="truncate text-[11px] text-muted-foreground">{hint}</span> : null}
33
+ </div>
34
+ {children}
35
+ </section>
36
+ )
37
+ }
38
+
39
+ const ADVANCED_OPEN_KEY = 'pascal-build-advanced-open'
40
+
41
+ function readAdvancedOpen(): boolean {
42
+ try {
43
+ return window.localStorage.getItem(ADVANCED_OPEN_KEY) === '1'
44
+ } catch {
45
+ return false
46
+ }
47
+ }
48
+
49
+ function writeAdvancedOpen(open: boolean) {
50
+ try {
51
+ window.localStorage.setItem(ADVANCED_OPEN_KEY, open ? '1' : '0')
52
+ } catch {
53
+ // Storage blocked (private window, preview): the section just starts closed.
54
+ }
55
+ }
56
+
57
+ /**
58
+ * The collapsible Advanced group. Remembers its open state per viewer, and
59
+ * opens itself when one of its tools becomes the armed one (e.g. by shortcut)
60
+ * so the lit tile is never hidden.
61
+ */
62
+ export function BuildPanelAdvancedSection({
63
+ children,
64
+ containsActiveTool,
65
+ description,
66
+ hint,
67
+ }: {
68
+ children: ReactNode
69
+ containsActiveTool: boolean
70
+ description?: string
71
+ hint?: string
72
+ }) {
73
+ const [open, setOpen] = useState(false)
74
+ useEffect(() => setOpen(readAdvancedOpen()), [])
75
+ useEffect(() => {
76
+ if (containsActiveTool) setOpen(true)
77
+ }, [containsActiveTool])
78
+
79
+ return (
80
+ <section className="flex flex-col" data-build-group="advanced">
81
+ <button
82
+ aria-expanded={open}
83
+ className="-mx-1.5 flex items-center gap-2 rounded-lg px-1.5 py-1.5 text-left transition-colors hover:bg-muted/60"
84
+ onClick={() => {
85
+ const next = !open
86
+ setOpen(next)
87
+ writeAdvancedOpen(next)
88
+ }}
89
+ type="button"
90
+ >
91
+ <ChevronRight
92
+ className={cn(
93
+ 'size-3 shrink-0 text-muted-foreground transition-transform duration-150',
94
+ open && 'rotate-90',
95
+ )}
96
+ />
97
+ <h3 className="font-medium text-foreground text-xs">Advanced</h3>
98
+ {hint ? (
99
+ <span className="ml-auto truncate text-[11px] text-muted-foreground">{hint}</span>
100
+ ) : null}
101
+ </button>
102
+ {open ? (
103
+ <div className="flex flex-col gap-2.5 pt-1.5">
104
+ {description ? (
105
+ <p className="px-0.5 text-[11px] text-muted-foreground leading-relaxed">
106
+ {description}
107
+ </p>
108
+ ) : null}
109
+ {children}
110
+ </div>
111
+ ) : null}
112
+ </section>
113
+ )
114
+ }
115
+
116
+ export function BuildToolGrid({ children, columns }: { children: ReactNode; columns: 3 | 4 }) {
117
+ return (
118
+ <div className={cn('grid gap-1.5', columns === 3 ? 'grid-cols-3' : 'grid-cols-4')}>
119
+ {children}
120
+ </div>
121
+ )
122
+ }
123
+
124
+ type BuildToolTileProps = Omit<ComponentPropsWithoutRef<'button'>, 'children' | 'type'> & {
125
+ active: boolean
126
+ /** Corner mark, e.g. the snap-target badge of wall-hosted tools. */
127
+ badge?: ReactNode
128
+ iconSrc: string
129
+ label: string
130
+ size?: 'lg' | 'sm'
131
+ }
132
+
133
+ /**
134
+ * One tool tile: the isometric icon over its name. Inactive icons stay grey and
135
+ * take their colour on hover; the armed tool keeps it, on the primary tint.
136
+ */
137
+ export function BuildToolTile({
138
+ active,
139
+ badge,
140
+ className,
141
+ iconSrc,
142
+ label,
143
+ size = 'sm',
144
+ ...buttonProps
145
+ }: BuildToolTileProps) {
146
+ return (
147
+ <button
148
+ aria-pressed={active}
149
+ className={cn(
150
+ 'group relative flex min-w-0 flex-col items-center justify-end rounded-xl transition-all duration-200',
151
+ // Small tiles grow to two label lines ("Spawn point"); a grid row's
152
+ // tiles stretch together, so a row keeps one height.
153
+ size === 'lg' ? 'h-[104px] gap-1.5 px-1.5 pt-2 pb-2.5' : 'min-h-[74px] gap-1 px-1 pt-1.5 pb-2',
154
+ active ? 'bg-primary/10 ring-1 ring-primary/50' : 'bg-muted/40 hover:bg-muted',
155
+ className,
156
+ )}
157
+ type="button"
158
+ {...buttonProps}
159
+ >
160
+ <img
161
+ alt=""
162
+ className={cn(
163
+ 'min-h-0 object-contain transition duration-200 group-hover:scale-110',
164
+ size === 'lg' ? 'size-14' : 'size-9',
165
+ !active && 'opacity-70 grayscale group-hover:opacity-100 group-hover:grayscale-0',
166
+ )}
167
+ draggable={false}
168
+ src={iconSrc}
169
+ />
170
+ <span
171
+ className={cn(
172
+ 'line-clamp-2 max-w-full text-center font-medium leading-tight',
173
+ size === 'lg' ? 'text-xs' : 'text-[11px]',
174
+ active ? 'text-foreground' : 'text-muted-foreground group-hover:text-foreground',
175
+ )}
176
+ >
177
+ {label}
178
+ </span>
179
+ {badge ? <span className="absolute top-1 right-1">{badge}</span> : null}
180
+ </button>
181
+ )
182
+ }
183
+
184
+ /** The line under the Rooms tiles: what the armed shape does, lead in bold. */
185
+ export function BuildPanelCaption({ lead, rest }: { lead?: string; rest: string }) {
186
+ return (
187
+ <p className="mt-2 min-h-[2lh] px-0.5 text-[11px] text-muted-foreground leading-relaxed">
188
+ {lead ? <span className="font-medium text-foreground">{lead} </span> : null}
189
+ {rest}
190
+ </p>
191
+ )
192
+ }
193
+
194
+ /**
195
+ * The Rooms group: one large tile per wall drawing variant, and the armed
196
+ * variant's caption. `activeVariant` is null while the wall tool is not armed.
197
+ */
198
+ export function BuildPanelRoomsSection({
199
+ activeVariant,
200
+ onHover,
201
+ onSelect,
202
+ tileProps,
203
+ }: {
204
+ activeVariant: WallDrawVariant | null
205
+ onHover?: (variant: WallDrawVariant) => void
206
+ onSelect: (variant: WallDrawVariant) => void
207
+ /** Extra attributes per tile (e.g. a host's guide-target hook). */
208
+ tileProps?: (variant: WallDrawVariant) => Record<string, string | undefined>
209
+ }) {
210
+ const active = WALL_DRAW_VARIANTS.find((variant) => variant.id === activeVariant)
211
+ return (
212
+ <BuildPanelSection hint="walls · floor · ceiling" id="rooms" title="Rooms">
213
+ <BuildToolGrid columns={3}>
214
+ {WALL_DRAW_VARIANTS.map((variant) => (
215
+ <BuildToolTile
216
+ active={variant.id === activeVariant}
217
+ data-build-tool={`room-${variant.id}`}
218
+ iconSrc={variant.iconSrc}
219
+ key={variant.id}
220
+ label={variant.label}
221
+ onClick={() => onSelect(variant.id)}
222
+ onMouseEnter={() => onHover?.(variant.id)}
223
+ size="lg"
224
+ title={variant.title}
225
+ {...tileProps?.(variant.id)}
226
+ />
227
+ ))}
228
+ </BuildToolGrid>
229
+ <BuildPanelCaption
230
+ lead={active?.caption.lead}
231
+ rest={active ? active.caption.rest : WALL_DRAW_IDLE_CAPTION}
232
+ />
233
+ </BuildPanelSection>
234
+ )
235
+ }
@@ -31,7 +31,6 @@ import {
31
31
  Redo2,
32
32
  Sparkles,
33
33
  Square,
34
- SquareStack,
35
34
  Sun,
36
35
  Trash2,
37
36
  Undo2,
@@ -40,6 +39,7 @@ import {
40
39
  import { useEffect } from 'react'
41
40
  import { getHistoryCommandState, runRedo, runUndo } from '../../../lib/history'
42
41
  import { deleteLevelWithFallbackSelection } from '../../../lib/level-selection'
42
+ import { getSavedSceneDocument } from '../../../lib/scene'
43
43
  import { createUnitInBuilding, enterUnitFocus, leaveUnitFocus } from '../../../lib/units'
44
44
  import { useCommandRegistry } from '../../../store/use-command-registry'
45
45
  import type { StructureTool } from '../../../store/use-editor'
@@ -50,10 +50,8 @@ export function EditorCommands() {
50
50
  const register = useCommandRegistry((s) => s.register)
51
51
  const { navigateTo, setInputValue, setOpen } = useCommandPalette()
52
52
 
53
- const setPhase = useEditor((s) => s.setPhase)
54
53
  const armToolMode = useEditor((s) => s.armToolMode)
55
54
  const armMaterialPaint = useEditor((s) => s.armMaterialPaint)
56
- const setStructureLayer = useEditor((s) => s.setStructureLayer)
57
55
  const isPreviewMode = useEditor((s) => s.isPreviewMode)
58
56
  const setPreviewMode = useEditor((s) => s.setPreviewMode)
59
57
 
@@ -79,12 +77,7 @@ export function EditorCommands() {
79
77
  )
80
78
 
81
79
  const activateTool = (tool: StructureTool) => {
82
- run(() => {
83
- setPhase('structure')
84
- if (tool === 'zone') setStructureLayer('zones')
85
- else setStructureLayer('elements')
86
- armToolMode({ mode: 'build', tool })
87
- })
80
+ run(() => armToolMode({ mode: 'build', tool }))
88
81
  }
89
82
 
90
83
  return register([
@@ -175,11 +168,7 @@ export function EditorCommands() {
175
168
  keywords: ['paint', 'material', 'texture', 'bucket', 'surface'],
176
169
  shortcut: ['P'],
177
170
  execute: () =>
178
- run(() => {
179
- setPhase('structure')
180
- setStructureLayer('elements')
181
- armMaterialPaint()
182
- }),
171
+ run(() => armMaterialPaint()),
183
172
  },
184
173
  {
185
174
  id: 'editor.mode.terrain-sculpt',
@@ -315,19 +304,6 @@ export function EditorCommands() {
315
304
  navigate: true,
316
305
  execute: () => navigateTo('wall-mode'),
317
306
  },
318
- {
319
- id: 'editor.viewer.level-mode',
320
- label: 'Level Mode',
321
- group: 'Viewer Controls',
322
- icon: <SquareStack className="h-4 w-4" />,
323
- keywords: ['level', 'floor', 'exploded', 'stacked', 'solo'],
324
- badge: () => {
325
- const mode = useViewer.getState().levelMode
326
- return { manual: 'Manual', stacked: 'Stacked', exploded: 'Exploded', solo: 'Solo' }[mode]
327
- },
328
- navigate: true,
329
- execute: () => navigateTo('level-mode'),
330
- },
331
307
  {
332
308
  id: 'editor.viewer.camera-mode',
333
309
  label: () => {
@@ -422,7 +398,7 @@ export function EditorCommands() {
422
398
  keywords: ['export', 'download', 'json', 'save', 'data'],
423
399
  execute: () =>
424
400
  run(() => {
425
- const { nodes, rootNodeIds } = useScene.getState()
401
+ const { nodes, rootNodeIds } = getSavedSceneDocument()
426
402
  const blob = new Blob([JSON.stringify({ nodes, rootNodeIds }, null, 2)], {
427
403
  type: 'application/json',
428
404
  })
@@ -476,10 +452,8 @@ export function EditorCommands() {
476
452
  navigateTo,
477
453
  setInputValue,
478
454
  setOpen,
479
- setPhase,
480
455
  armToolMode,
481
456
  armMaterialPaint,
482
- setStructureLayer,
483
457
  isPreviewMode,
484
458
  setPreviewMode,
485
459
  exportScene,
@@ -149,7 +149,6 @@ function OptionItem({
149
149
  // ---------------------------------------------------------------------------
150
150
  const PAGE_LABEL: Record<string, string> = {
151
151
  'wall-mode': 'Wall Mode',
152
- 'level-mode': 'Level Mode',
153
152
  'rename-level': 'Rename Level',
154
153
  'goto-level': 'Go to Level',
155
154
  }
@@ -204,8 +203,6 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
204
203
 
205
204
  const wallMode = useViewer((s) => s.wallMode)
206
205
  const setWallMode = useViewer((s) => s.setWallMode)
207
- const levelMode = useViewer((s) => s.levelMode)
208
- const setLevelMode = useViewer((s) => s.setLevelMode)
209
206
 
210
207
  const allLevels = useScene(
211
208
  useShallow((s) =>
@@ -250,12 +247,6 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
250
247
  down: 'Down',
251
248
  translucent: 'Translucent',
252
249
  }
253
- const levelModeLabel: Record<'manual' | 'stacked' | 'exploded' | 'solo', string> = {
254
- manual: 'Manual',
255
- stacked: 'Stacked',
256
- exploded: 'Exploded',
257
- solo: 'Solo',
258
- }
259
250
 
260
251
  const confirmRename = () => {
261
252
  if (!(activeLevelId && inputValue.trim())) return
@@ -364,7 +355,11 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
364
355
  keywords={action.keywords}
365
356
  label={action.label}
366
357
  navigate={action.navigate}
367
- onSelect={() => action.execute()}
358
+ // A registered command that does not navigate within the
359
+ // palette closes it, like the built-ins — otherwise the
360
+ // dialog overlay keeps swallowing clicks under a full-screen
361
+ // surface the command opened (Sheets).
362
+ onSelect={() => (action.navigate ? action.execute() : run(() => action.execute()))}
368
363
  shortcut={action.shortcut}
369
364
  />
370
365
  ))}
@@ -385,20 +380,6 @@ export function CommandPalette({ emptyAction }: { emptyAction?: CommandPaletteEm
385
380
  </Command.Group>
386
381
  )}
387
382
 
388
- {/* ── Level Mode sub-page ───────────────────────────────────── */}
389
- {page === 'level-mode' && (
390
- <Command.Group heading="Level Mode">
391
- {(['stacked', 'exploded', 'solo'] as const).map((mode) => (
392
- <OptionItem
393
- isActive={levelMode === mode}
394
- key={mode}
395
- label={levelModeLabel[mode]}
396
- onSelect={() => run(() => setLevelMode(mode))}
397
- />
398
- ))}
399
- </Command.Group>
400
- )}
401
-
402
383
  {/* ── Go to Level sub-page ──────────────────────────────────── */}
403
384
  {page === 'goto-level' && (
404
385
  <Command.Group heading="Go to Level">
@@ -0,0 +1,110 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNode,
5
+ getCatalogMaterialById,
6
+ getLibraryMaterialIdFromRef,
7
+ getSceneMaterialIdFromRef,
8
+ nodeRegistry,
9
+ useScene,
10
+ } from '@pascal-app/core'
11
+ import { useState } from 'react'
12
+ import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover'
13
+ import { MaterialPicker } from './material-picker'
14
+
15
+ type Swatch = { label: string; color: string | null; image: string | null }
16
+
17
+ /** How a finish reference reads in a panel: its catalog tile, its colour, or its scene material. */
18
+ function useFinishSwatch(ref: string | undefined, fallback: string): Swatch {
19
+ const materials = useScene((state) => state.materials)
20
+ if (!ref) return { ...describe(fallback, materials), label: 'Default' }
21
+ return describe(ref, materials)
22
+ }
23
+
24
+ function describe(ref: string, materials: ReturnType<typeof useScene.getState>['materials']) {
25
+ const library = getCatalogMaterialById(getLibraryMaterialIdFromRef(ref) ?? undefined)
26
+ if (library)
27
+ return {
28
+ label: library.label,
29
+ color: library.previewColor ?? null,
30
+ image: library.previewThumbnailUrl ?? null,
31
+ }
32
+ const sceneId = getSceneMaterialIdFromRef(ref)
33
+ const scene = sceneId ? materials[sceneId as keyof typeof materials] : undefined
34
+ if (scene) return { label: scene.name, color: scene.material.properties?.color ?? null, image: null }
35
+ if (ref.startsWith('#')) return { label: ref.toUpperCase(), color: ref, image: null }
36
+ return { label: 'Default', color: null, image: null }
37
+ }
38
+
39
+ /**
40
+ * Paints one surface of a node from its panel, exactly as the paint tool does:
41
+ * the kind's own paint commit for that role (same write, one undo step).
42
+ */
43
+ export function paintSurface(node: AnyNode, role: string, materialPreset: string) {
44
+ const paint = nodeRegistry.get(node.type)?.capabilities?.paint
45
+ if (!paint) return
46
+ const args = { node, role, material: undefined, materialPreset }
47
+ if (paint.commit) paint.commit(args)
48
+ else useScene.getState().updateNode(node.id, paint.buildPatch(args) as Partial<AnyNode>)
49
+ }
50
+
51
+ /**
52
+ * A finish row: the swatch and its name. A click opens the paint tool's
53
+ * material picker; a pick paints the surface through the paint tool's path.
54
+ */
55
+ export function FinishSwatch({
56
+ label,
57
+ node,
58
+ role,
59
+ value,
60
+ fallback,
61
+ hint,
62
+ }: {
63
+ label: string
64
+ node: AnyNode
65
+ /** The paint role of the surface (`edge`, `foundation`, …). */
66
+ role: string
67
+ /** The authored finish reference; absent shows `fallback` as "Default". */
68
+ value: string | undefined
69
+ fallback: string
70
+ /** Tooltip on the row. */
71
+ hint?: string
72
+ }) {
73
+ const [open, setOpen] = useState(false)
74
+ const swatch = useFinishSwatch(value, fallback)
75
+ return (
76
+ <div className="flex min-h-8 items-center justify-between gap-3" title={hint}>
77
+ <span className="text-muted-foreground text-xs">{label}</span>
78
+ <Popover onOpenChange={setOpen} open={open}>
79
+ <PopoverTrigger asChild>
80
+ <button
81
+ aria-label={`${label}: ${swatch.label}`}
82
+ className="flex min-w-0 items-center gap-2 rounded-full border border-border/50 py-0.5 pr-2.5 pl-1 text-xs transition-colors hover:bg-accent"
83
+ data-finish-swatch={label}
84
+ type="button"
85
+ >
86
+ <span
87
+ aria-hidden
88
+ className="size-4 shrink-0 overflow-hidden rounded-full border border-border/50"
89
+ style={{ backgroundColor: swatch.color ?? 'transparent' }}
90
+ >
91
+ {swatch.image && <img alt="" className="size-full object-cover" src={swatch.image} />}
92
+ </span>
93
+ <span className="truncate">{swatch.label}</span>
94
+ </button>
95
+ </PopoverTrigger>
96
+ <PopoverContent align="end" className="flex h-96 w-80 flex-col p-2" side="left">
97
+ <MaterialPicker
98
+ onSelectMaterialPreset={(ref) => {
99
+ const live = useScene.getState().nodes[node.id as keyof ReturnType<typeof useScene.getState>['nodes']]
100
+ if (live) paintSurface(live, role, ref)
101
+ setOpen(false)
102
+ }}
103
+ selectOnCategoryChange={false}
104
+ selectedMaterialPreset={value}
105
+ />
106
+ </PopoverContent>
107
+ </Popover>
108
+ </div>
109
+ )
110
+ }