@pascal-app/editor 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (624) hide show
  1. package/README.md +106 -0
  2. package/package.json +42 -38
  3. package/src/components/editor/__bench__/room-label-placement.bench.ts +108 -0
  4. package/src/components/editor/action-menu-button.tsx +83 -0
  5. package/src/components/editor/bake-thumbnail.tsx +307 -5
  6. package/src/components/editor/capture-level-picker.tsx +72 -0
  7. package/src/components/editor/construction-reveal-audio.test.tsx +150 -0
  8. package/src/components/editor/construction-reveal-audio.tsx +40 -0
  9. package/src/components/editor/custom-camera-controls.tsx +275 -57
  10. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  11. package/src/components/editor/duplicate-node.ts +244 -0
  12. package/src/components/editor/editor-layout-mobile.tsx +37 -26
  13. package/src/components/editor/editor-layout-v2.tsx +132 -59
  14. package/src/components/editor/export-manager.tsx +47 -26
  15. package/src/components/editor/first-person/build-collider-world.test.ts +320 -0
  16. package/src/components/editor/first-person/build-collider-world.ts +28 -7
  17. package/src/components/editor/first-person/ground-holes.test.ts +128 -0
  18. package/src/components/editor/first-person/ground-holes.ts +130 -0
  19. package/src/components/editor/first-person/mechanism-targets.test.ts +162 -0
  20. package/src/components/editor/first-person/mechanism-targets.ts +88 -0
  21. package/src/components/editor/first-person-controls.tsx +220 -9
  22. package/src/components/editor/floating-action-menu.tsx +63 -207
  23. package/src/components/editor/floor-aware-camera-controls.test.ts +466 -0
  24. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  25. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  26. package/src/components/editor/floor-region-controls.tsx +846 -0
  27. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  28. package/src/components/editor/floor-surface.test.ts +165 -0
  29. package/src/components/editor/floor-surface.ts +182 -0
  30. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  31. package/src/components/editor/floorplan-background-selection.ts +1 -3
  32. package/src/components/editor/floorplan-mode-coordinator.tsx +2 -2
  33. package/src/components/editor/floorplan-panel.tsx +414 -363
  34. package/src/components/editor/follow-pascal.test.tsx +912 -0
  35. package/src/components/editor/follow-pascal.tsx +354 -0
  36. package/src/components/editor/footprint-height-handle.tsx +204 -0
  37. package/src/components/editor/group-actions.ts +56 -2
  38. package/src/components/editor/group-move-3d.ts +2 -0
  39. package/src/components/editor/group-rotate-handle.tsx +2 -0
  40. package/src/components/editor/group-transform-shared.test.ts +460 -0
  41. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  42. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  43. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  45. package/src/components/editor/index.tsx +409 -197
  46. package/src/components/editor/join-walls-pill.tsx +44 -0
  47. package/src/components/editor/node-action-menu.tsx +68 -70
  48. package/src/components/editor/node-arrow-handles.tsx +25 -6
  49. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  50. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  52. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  53. package/src/components/editor/registry-action-contributions.tsx +80 -1
  54. package/src/components/editor/riser-diagram-panel.tsx +14 -7
  55. package/src/components/editor/room-controls.tsx +847 -0
  56. package/src/components/editor/room-floor-highlight.tsx +128 -0
  57. package/src/components/editor/room-handles-2d.tsx +354 -0
  58. package/src/components/editor/room-handles.tsx +675 -0
  59. package/src/components/editor/room-highlight.tsx +233 -0
  60. package/src/components/editor/room-labels.test.ts +850 -0
  61. package/src/components/editor/room-labels.tsx +1890 -0
  62. package/src/components/editor/room-transform-ghost.tsx +256 -0
  63. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  64. package/src/components/editor/selection-manager.tsx +474 -419
  65. package/src/components/editor/slab-hole-highlights.tsx +5 -2
  66. package/src/components/editor/snapshot-capture-overlay.tsx +7 -2
  67. package/src/components/editor/snapshot-capture.test.ts +119 -60
  68. package/src/components/editor/snapshot-capture.ts +36 -5
  69. package/src/components/editor/thumbnail-generator.test.ts +654 -0
  70. package/src/components/editor/thumbnail-generator.tsx +1103 -223
  71. package/src/components/editor/use-floorplan-background-placement.ts +34 -12
  72. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  73. package/src/components/editor/views/host-view.tsx +61 -0
  74. package/src/components/editor/views/plugins-view.tsx +663 -0
  75. package/src/components/editor/views/scene-region.tsx +59 -0
  76. package/src/components/editor/views/use-editor-views.ts +57 -0
  77. package/src/components/editor/views/view-bar.test.tsx +54 -0
  78. package/src/components/editor/views/view-bar.tsx +343 -0
  79. package/src/components/editor/wall-justification.test.ts +140 -0
  80. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  81. package/src/components/editor/wall-push-arrow.tsx +92 -0
  82. package/src/components/editor/wall-region-handles.tsx +509 -0
  83. package/src/components/editor/wall-snap-beacon-layer.tsx +8 -4
  84. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  85. package/src/components/editor-2d/floorplan-group-move.tsx +3 -0
  86. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  87. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  88. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +2 -2
  89. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  90. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +116 -106
  91. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  92. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  93. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  94. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  95. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  96. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  97. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +319 -233
  98. package/src/components/editor-2d/renderers/floorplan-reveal.test.tsx +254 -0
  99. package/src/components/editor-2d/renderers/floorplan-reveal.ts +141 -0
  100. package/src/components/editor-2d/svg-paths.ts +12 -15
  101. package/src/components/point-ask/bubble.tsx +538 -0
  102. package/src/components/point-ask/hover-chip.tsx +91 -0
  103. package/src/components/point-ask/mode-pill.tsx +92 -0
  104. package/src/components/point-ask/optional-motion.ts +22 -0
  105. package/src/components/point-ask/outline-corners.tsx +93 -0
  106. package/src/components/point-ask/pins.tsx +711 -0
  107. package/src/components/point-ask/point-ask-layer.tsx +83 -0
  108. package/src/components/point-ask/point-ask-styles.ts +105 -0
  109. package/src/components/point-ask/region-marquee-styles.ts +15 -0
  110. package/src/components/point-ask/region-marquee.tsx +64 -0
  111. package/src/components/procedural-items/inspector.tsx +40 -15
  112. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +12 -2
  113. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  114. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  115. package/src/components/systems/roof/roof-edit-system.tsx +1 -0
  116. package/src/components/systems/zone/zone-system.test.ts +47 -0
  117. package/src/components/systems/zone/zone-system.tsx +29 -68
  118. package/src/components/tools/distribution/connection-compatibility.test.ts +34 -0
  119. package/src/components/tools/distribution/connection-compatibility.ts +43 -0
  120. package/src/components/tools/distribution/distribution-public.ts +9 -0
  121. package/src/components/tools/distribution/distribution-run-contract.test.ts +26 -0
  122. package/src/components/tools/distribution/distribution-run-contract.ts +94 -0
  123. package/src/components/tools/distribution/distribution-run-tool.test.ts +241 -0
  124. package/src/components/tools/distribution/distribution-run-tool.test.tsx +125 -0
  125. package/src/components/tools/distribution/distribution-run-tool.tsx +1323 -0
  126. package/src/components/tools/distribution/draw-alignment.ts +35 -0
  127. package/src/components/tools/distribution/ports.test.ts +56 -0
  128. package/src/components/tools/distribution/ports.ts +378 -0
  129. package/src/components/tools/distribution/run-cursor.test.ts +135 -0
  130. package/src/components/tools/distribution/run-cursor.ts +36 -0
  131. package/src/components/tools/distribution/run-direction-feedback.test.ts +60 -0
  132. package/src/components/tools/distribution/run-direction-feedback.tsx +251 -0
  133. package/src/components/tools/distribution/run-port-snap.test.ts +79 -0
  134. package/src/components/tools/distribution/run-port-snap.ts +25 -0
  135. package/src/components/tools/distribution/run-screen-direction.test.ts +23 -0
  136. package/src/components/tools/distribution/run-screen-direction.ts +30 -0
  137. package/src/components/tools/elevator/elevator-tool.tsx +3 -6
  138. package/src/components/tools/elevator/move-elevator-tool.tsx +2 -3
  139. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +67 -68
  140. package/src/components/tools/item/placement-history.test.tsx +512 -0
  141. package/src/components/tools/item/placement-strategies.test.ts +46 -1
  142. package/src/components/tools/item/placement-strategies.ts +11 -10
  143. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +56 -56
  144. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  145. package/src/components/tools/item/use-draft-node.ts +166 -95
  146. package/src/components/tools/item/use-placement-coordinator.tsx +148 -95
  147. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  148. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  149. package/src/components/tools/registry/item-surface-move.test.ts +62 -63
  150. package/src/components/tools/registry/item-surface-move.ts +2 -0
  151. package/src/components/tools/registry/item-surface-resolver.test.ts +26 -26
  152. package/src/components/tools/registry/move-registry-node-tool.test.ts +268 -1
  153. package/src/components/tools/registry/move-registry-node-tool.tsx +66 -30
  154. package/src/components/tools/select/box-select-tool.tsx +2 -12
  155. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  156. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  157. package/src/components/tools/select/select-candidates.test.ts +37 -4
  158. package/src/components/tools/select/select-candidates.ts +15 -11
  159. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  160. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  161. package/src/components/tools/tool-manager.tsx +59 -24
  162. package/src/components/tools/wall/wall-drafting.test.ts +175 -47
  163. package/src/components/tools/zone/zone-tool.tsx +4 -12
  164. package/src/components/ui/action-menu/action-button.tsx +9 -1
  165. package/src/components/ui/action-menu/camera-actions.tsx +9 -4
  166. package/src/components/ui/action-menu/control-modes.tsx +15 -42
  167. package/src/components/ui/action-menu/follow-pascal-pill.tsx +32 -0
  168. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +36 -0
  169. package/src/components/ui/action-menu/index.tsx +56 -33
  170. package/src/components/ui/action-menu/measurement-control.tsx +14 -19
  171. package/src/components/ui/action-menu/placement.ts +20 -0
  172. package/src/components/ui/action-menu/point-ask-button.tsx +52 -0
  173. package/src/components/ui/action-menu/view-toggles.tsx +47 -42
  174. package/src/components/ui/build-panel/build-panel.tsx +242 -0
  175. package/src/components/ui/command-palette/editor-commands.tsx +7 -42
  176. package/src/components/ui/command-palette/index.tsx +50 -39
  177. package/src/components/ui/command-palette/ranked-groups.test.ts +31 -0
  178. package/src/components/ui/controls/action-button.tsx +5 -2
  179. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  180. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  181. package/src/components/ui/controls/material-picker.tsx +4 -1
  182. package/src/components/ui/controls/metric-control.tsx +1 -1
  183. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  184. package/src/components/ui/controls/segmented-control.tsx +21 -8
  185. package/src/components/ui/controls/select-control.tsx +65 -0
  186. package/src/components/ui/controls/slider-control.tsx +1 -1
  187. package/src/components/ui/controls/toggle-control.tsx +3 -3
  188. package/src/components/ui/controls/tool-options-panel.tsx +1 -1
  189. package/src/components/ui/floating-level-selector.tsx +159 -59
  190. package/src/components/ui/helpers/building-helper.tsx +4 -1
  191. package/src/components/ui/helpers/contextual-helper-panel.tsx +181 -72
  192. package/src/components/ui/helpers/helper-manager.test.tsx +121 -0
  193. package/src/components/ui/helpers/helper-manager.tsx +331 -14
  194. package/src/components/ui/helpers/item-helper.tsx +17 -1
  195. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  196. package/src/components/ui/icon-ref.tsx +45 -1
  197. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  198. package/src/components/ui/node-icon-image.tsx +16 -0
  199. package/src/components/ui/panels/collections/collections-popover.tsx +28 -22
  200. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  201. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  202. package/src/components/ui/panels/mobile-panel-sheet.tsx +4 -4
  203. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -5
  204. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  205. package/src/components/ui/panels/multi-field-value.ts +6 -3
  206. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  207. package/src/components/ui/panels/multi-selection-panel.tsx +161 -5
  208. package/src/components/ui/panels/node-display.ts +8 -3
  209. package/src/components/ui/panels/panel-manager.tsx +103 -68
  210. package/src/components/ui/panels/panel-wrapper.tsx +130 -62
  211. package/src/components/ui/panels/parametric-field-control.tsx +3 -3
  212. package/src/components/ui/panels/parametric-inspector.tsx +7 -7
  213. package/src/components/ui/panels/reference-panel.tsx +2 -3
  214. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  215. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  216. package/src/components/ui/panels/room-name-options.ts +47 -0
  217. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  218. package/src/components/ui/panels/room-panel.tsx +790 -0
  219. package/src/components/ui/panels/shape-choice.tsx +58 -0
  220. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  221. package/src/components/ui/primitives/badge.tsx +8 -0
  222. package/src/components/ui/primitives/color-dot.tsx +36 -24
  223. package/src/components/ui/primitives/sidebar.tsx +3 -1
  224. package/src/components/ui/right-stack.test.ts +29 -0
  225. package/src/components/ui/right-stack.tsx +164 -0
  226. package/src/components/ui/sidebar/app-sidebar.tsx +1 -1
  227. package/src/components/ui/sidebar/icon-rail.tsx +8 -3
  228. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  229. package/src/components/ui/sidebar/panels/items-panel/index.tsx +6 -6
  230. package/src/components/ui/sidebar/panels/settings-panel/animation-speed-setting.tsx +40 -0
  231. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +7 -8
  232. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.test.tsx +170 -0
  233. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +92 -0
  234. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +138 -27
  235. package/src/components/ui/sidebar/panels/settings-panel/interface-theme.test.tsx +20 -0
  236. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  237. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +4 -10
  238. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -11
  239. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  240. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  241. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  242. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  243. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  244. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  245. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  246. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  247. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  248. package/src/components/ui/sidebar/panels/site-panel/index.tsx +181 -223
  249. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  250. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  251. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  252. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  253. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  254. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  255. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  256. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  257. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  258. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  259. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +13 -37
  260. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  261. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  262. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  263. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  264. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +2 -4
  265. package/src/components/ui/sidebar/tab-bar.tsx +14 -4
  266. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  267. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  268. package/src/components/viewer/floorplan-preview.tsx +5 -6
  269. package/src/components/viewer/viewer-controls-bar.tsx +18 -6
  270. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  271. package/src/components/viewer/viewer-rooms.tsx +191 -0
  272. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  273. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  274. package/src/components/viewer/viewer-stage.tsx +2 -2
  275. package/src/components/viewer-zone-system.tsx +3 -8
  276. package/src/hooks/use-auto-save.integration.test.tsx +215 -43
  277. package/src/hooks/use-auto-save.ts +60 -9
  278. package/src/hooks/use-ceiling-events.test.ts +123 -118
  279. package/src/hooks/use-grid-events.test.ts +105 -105
  280. package/src/hooks/use-keyboard.test.ts +243 -19
  281. package/src/hooks/use-keyboard.ts +118 -83
  282. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  283. package/src/hooks/use-open-wall-ends.ts +238 -0
  284. package/src/hooks/use-placement-notice.test.ts +71 -0
  285. package/src/hooks/use-placement-notice.ts +73 -0
  286. package/src/hooks/use-selected-room.ts +143 -0
  287. package/src/index.tsx +312 -58
  288. package/src/lib/__fixtures__/owner-victor-seattle-parking.json +1 -0
  289. package/src/lib/__fixtures__/owner-villa-aurelia-basement.json +1 -0
  290. package/src/lib/add-storey.test.ts +177 -0
  291. package/src/lib/add-storey.ts +59 -0
  292. package/src/lib/below-grade.test.tsx +171 -0
  293. package/src/lib/below-grade.ts +133 -0
  294. package/src/lib/camera-facing.test.ts +24 -0
  295. package/src/lib/camera-facing.ts +24 -0
  296. package/src/lib/ceiling-edit-session.test.ts +321 -0
  297. package/src/lib/ceiling-edit-session.ts +206 -0
  298. package/src/lib/ceiling-region-session.test.ts +179 -0
  299. package/src/lib/continuation.test.ts +19 -1
  300. package/src/lib/continuation.ts +25 -4
  301. package/src/lib/direct-manipulation.ts +2 -0
  302. package/src/lib/door-interaction.ts +14 -0
  303. package/src/lib/download-project-file.ts +17 -0
  304. package/src/lib/editor-level-display.test.ts +96 -0
  305. package/src/lib/editor-level-display.ts +55 -0
  306. package/src/lib/editor-views.ts +178 -0
  307. package/src/lib/export-geometry-wait.test.ts +90 -0
  308. package/src/lib/export-texture-utils.ts +2 -4
  309. package/src/lib/floor-edit-session.ts +153 -0
  310. package/src/lib/floor-footprints.test.ts +563 -0
  311. package/src/lib/floor-footprints.ts +395 -0
  312. package/src/lib/floor-foundation-support.test.ts +284 -0
  313. package/src/lib/floor-opening-draft.ts +218 -0
  314. package/src/lib/floor-openings.test.ts +216 -0
  315. package/src/lib/floor-region-geometry.ts +191 -0
  316. package/src/lib/floor-region-session.test.ts +485 -0
  317. package/src/lib/floor-region-session.ts +589 -0
  318. package/src/lib/floor-region-snap.ts +101 -0
  319. package/src/lib/floor-step-hover.test.ts +32 -0
  320. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  321. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  322. package/src/lib/floorplan/dimension-drive.ts +479 -0
  323. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  324. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  325. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  326. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  327. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  328. package/src/lib/floorplan/floorplan-pdfkit-document.ts +363 -27
  329. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +177 -8
  330. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +60 -25
  331. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  332. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  333. package/src/lib/floorplan/geometry.ts +13 -6
  334. package/src/lib/floorplan/index.ts +0 -9
  335. package/src/lib/floorplan/items.ts +5 -2
  336. package/src/lib/floorplan/marks.test.ts +213 -0
  337. package/src/lib/floorplan/marks.ts +238 -0
  338. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  339. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  340. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  341. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  342. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  343. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  344. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  345. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  346. package/src/lib/floorplan/pdfkit-standard-fonts.d.ts +5 -0
  347. package/src/lib/floorplan/schedules.test.ts +189 -0
  348. package/src/lib/floorplan/schedules.ts +339 -0
  349. package/src/lib/floorplan/sheet-export.test.ts +124 -0
  350. package/src/lib/floorplan/sheet-export.ts +318 -0
  351. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +768 -0
  352. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  353. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  354. package/src/lib/floorplan/site-plan/coverage.ts +230 -0
  355. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  356. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  357. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  358. package/src/lib/floorplan/site-plan/front-edge.ts +157 -0
  359. package/src/lib/floorplan/site-plan/geometry.test.ts +269 -0
  360. package/src/lib/floorplan/site-plan/geometry.ts +142 -0
  361. package/src/lib/floorplan/site-plan/index.ts +55 -0
  362. package/src/lib/floorplan/site-plan/site-annotations.test.ts +483 -0
  363. package/src/lib/floorplan/site-plan/site-annotations.ts +1084 -0
  364. package/src/lib/floorplan/site-plan/site-parts.ts +287 -0
  365. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  366. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  367. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  368. package/src/lib/floorplan/site-plan/yards.test.ts +70 -0
  369. package/src/lib/floorplan/types.ts +1 -27
  370. package/src/lib/follow-pascal-exterior.test-fixture.ts +17 -0
  371. package/src/lib/follow-pascal-exterior.test.ts +171 -0
  372. package/src/lib/follow-pascal-exterior.ts +39 -0
  373. package/src/lib/follow-pascal-presentation.test.ts +203 -0
  374. package/src/lib/follow-pascal-presentation.ts +129 -0
  375. package/src/lib/follow-pascal-sight.ts +84 -0
  376. package/src/lib/follow-pascal-targets.test.ts +406 -0
  377. package/src/lib/follow-pascal-targets.ts +198 -0
  378. package/src/lib/follow-pascal.test.ts +350 -0
  379. package/src/lib/follow-pascal.ts +284 -0
  380. package/src/lib/fresh-planar-placement.test.ts +148 -0
  381. package/src/lib/fresh-planar-placement.ts +114 -37
  382. package/src/lib/geometry-script/author.ts +157 -0
  383. package/src/lib/geometry-script/client.ts +159 -0
  384. package/src/lib/geometry-script/finish-scripts.ts +127 -0
  385. package/src/lib/geometry-script/protocol.ts +13 -0
  386. package/src/lib/gesture-lifecycle.test.ts +595 -0
  387. package/src/lib/gesture-lifecycle.ts +290 -0
  388. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  389. package/src/lib/glb-export.test.ts +636 -126
  390. package/src/lib/glb-export.ts +241 -120
  391. package/src/lib/history.test.ts +18 -7
  392. package/src/lib/history.ts +7 -0
  393. package/src/lib/hud-title.ts +109 -0
  394. package/src/lib/hydration-gated-storage.ts +49 -0
  395. package/src/lib/ifc-export.test.ts +174 -0
  396. package/src/lib/ifc-export.ts +201 -0
  397. package/src/lib/ifc-parts.ts +15 -0
  398. package/src/lib/inspector-expanded.test.ts +44 -0
  399. package/src/lib/inspector-expanded.ts +36 -0
  400. package/src/lib/interaction/scope.ts +22 -0
  401. package/src/lib/keyboard-pan.test.ts +129 -7
  402. package/src/lib/keyboard-pan.ts +161 -12
  403. package/src/lib/level-selection.test.ts +57 -0
  404. package/src/lib/level-selection.ts +16 -1
  405. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  406. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  407. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  408. package/src/lib/lot/clean-ring.test.ts +187 -0
  409. package/src/lib/lot/clean-ring.ts +217 -0
  410. package/src/lib/lot/dossier.test.ts +202 -0
  411. package/src/lib/lot/dossier.ts +498 -0
  412. package/src/lib/lot/drop-in.test.ts +326 -0
  413. package/src/lib/lot/drop-in.ts +392 -0
  414. package/src/lib/lot/index.ts +43 -0
  415. package/src/lib/lot/lot-patch.test.ts +340 -0
  416. package/src/lib/lot/lot-patch.ts +394 -0
  417. package/src/lib/lot/parcel-provider.ts +43 -0
  418. package/src/lib/lot/terrain.test.ts +161 -0
  419. package/src/lib/lot/terrain.ts +266 -0
  420. package/src/lib/material-catalog-model.ts +3 -1
  421. package/src/lib/material-paint-panel-model.ts +9 -5
  422. package/src/lib/material-paint.ts +4 -0
  423. package/src/lib/measurement-kind.test.ts +6 -8
  424. package/src/lib/measurements.test.ts +23 -0
  425. package/src/lib/mezzanine-actions.test.ts +342 -0
  426. package/src/lib/mezzanine-draft.test.ts +501 -0
  427. package/src/lib/mezzanine-draft.ts +278 -0
  428. package/src/lib/mezzanine-messages.ts +26 -0
  429. package/src/lib/mezzanine-selection.test.ts +207 -0
  430. package/src/lib/mezzanine-stairs.ts +46 -0
  431. package/src/lib/model-export.ts +3 -1
  432. package/src/lib/node-action-movement.ts +17 -6
  433. package/src/lib/node-display-label.ts +37 -0
  434. package/src/lib/orbit-floor-clearance.test.ts +201 -0
  435. package/src/lib/orbit-floor-clearance.ts +154 -0
  436. package/src/lib/paint-commit-route.test.ts +37 -0
  437. package/src/lib/paint-commit-route.ts +17 -0
  438. package/src/lib/paint-eyedropper.test.tsx +165 -0
  439. package/src/lib/paint-eyedropper.ts +99 -0
  440. package/src/lib/paint-pass-through.test.ts +122 -0
  441. package/src/lib/paint-pass-through.ts +78 -0
  442. package/src/lib/paint-region-hover.ts +25 -0
  443. package/src/lib/paint-region-mode.ts +154 -0
  444. package/src/lib/paint-regions.test.ts +166 -0
  445. package/src/lib/paint-regions.ts +192 -0
  446. package/src/lib/paint-scope.test.ts +317 -35
  447. package/src/lib/paint-scope.ts +267 -25
  448. package/src/lib/panel-rows.ts +21 -0
  449. package/src/lib/parametric-node-update.ts +16 -7
  450. package/src/lib/plate-paint-affected.test.ts +300 -0
  451. package/src/lib/plate-paint-affected.ts +177 -0
  452. package/src/lib/plugin-chat.ts +65 -0
  453. package/src/lib/plugin-panels.test.ts +25 -0
  454. package/src/lib/plugin-panels.ts +94 -0
  455. package/src/lib/point-ask/__fixtures__/consumer-browser.tsx +76 -0
  456. package/src/lib/point-ask/__fixtures__/house.ts +81 -0
  457. package/src/lib/point-ask/ask-created.test.ts +195 -0
  458. package/src/lib/point-ask/ask-flow.test.ts +380 -0
  459. package/src/lib/point-ask/build-context.test.ts +173 -0
  460. package/src/lib/point-ask/build-context.ts +94 -0
  461. package/src/lib/point-ask/capture-crop.ts +129 -0
  462. package/src/lib/point-ask/choreography.ts +69 -0
  463. package/src/lib/point-ask/compare.test.ts +145 -0
  464. package/src/lib/point-ask/compare.ts +73 -0
  465. package/src/lib/point-ask/describe-target.test.ts +165 -0
  466. package/src/lib/point-ask/describe-target.ts +234 -0
  467. package/src/lib/point-ask/dictation.ts +79 -0
  468. package/src/lib/point-ask/edge-marker.test.ts +61 -0
  469. package/src/lib/point-ask/edge-marker.ts +27 -0
  470. package/src/lib/point-ask/frame-loop.ts +25 -0
  471. package/src/lib/point-ask/history-marks.test.ts +94 -0
  472. package/src/lib/point-ask/history-marks.ts +39 -0
  473. package/src/lib/point-ask/host-api.ts +32 -0
  474. package/src/lib/point-ask/outline-box.test.ts +30 -0
  475. package/src/lib/point-ask/outline-box.ts +11 -0
  476. package/src/lib/point-ask/overlay-metrics.ts +6 -0
  477. package/src/lib/point-ask/pick.test.ts +220 -0
  478. package/src/lib/point-ask/pick.ts +121 -0
  479. package/src/lib/point-ask/pin-machine.test.ts +494 -0
  480. package/src/lib/point-ask/pin-machine.ts +217 -0
  481. package/src/lib/point-ask/placement.test.ts +203 -0
  482. package/src/lib/point-ask/placement.ts +141 -0
  483. package/src/lib/point-ask/pointer-tracker.ts +34 -0
  484. package/src/lib/point-ask/projector.ts +83 -0
  485. package/src/lib/point-ask/region.test.ts +157 -0
  486. package/src/lib/point-ask/region.ts +136 -0
  487. package/src/lib/point-ask/retry.test.ts +217 -0
  488. package/src/lib/point-ask/retry.ts +37 -0
  489. package/src/lib/point-ask/scene-commit.test.ts +312 -0
  490. package/src/lib/point-ask/scene-commit.ts +32 -0
  491. package/src/lib/point-ask/session.ts +1391 -0
  492. package/src/lib/point-ask/spring-keyframes.test.ts +45 -0
  493. package/src/lib/point-ask/spring-keyframes.ts +38 -0
  494. package/src/lib/point-ask/spring.test.ts +63 -0
  495. package/src/lib/point-ask/spring.ts +107 -0
  496. package/src/lib/point-ask/suggestions.test.ts +80 -0
  497. package/src/lib/point-ask/suggestions.ts +125 -0
  498. package/src/lib/point-ask/target-scope.test.ts +622 -0
  499. package/src/lib/point-ask/target-scope.ts +338 -0
  500. package/src/lib/point-ask/types.ts +48 -0
  501. package/src/lib/portable-export.test.ts +34 -1
  502. package/src/lib/portable-export.ts +119 -53
  503. package/src/lib/print-feature-thickness.ts +10 -1
  504. package/src/lib/print-roof-solids.test.ts +39 -1
  505. package/src/lib/print-roof-solids.ts +43 -10
  506. package/src/lib/print-shell-compiler-manifold-core.ts +1 -1
  507. package/src/lib/print-shell-compiler.ts +1 -0
  508. package/src/lib/registered-draft-snap.ts +12 -0
  509. package/src/lib/roof-duplication.ts +10 -2
  510. package/src/lib/room-assembly-overlay.test.ts +275 -0
  511. package/src/lib/room-assembly-overlay.ts +265 -0
  512. package/src/lib/room-built-on.test.ts +379 -0
  513. package/src/lib/room-built-on.ts +138 -0
  514. package/src/lib/room-construction-commands.ts +712 -0
  515. package/src/lib/room-construction.test.ts +746 -0
  516. package/src/lib/room-construction.ts +385 -0
  517. package/src/lib/room-controls-phase6.test.ts +630 -0
  518. package/src/lib/room-divide-session.ts +523 -0
  519. package/src/lib/room-handle-drag.ts +1068 -0
  520. package/src/lib/room-name-catalog.test.ts +90 -0
  521. package/src/lib/room-name-catalog.ts +168 -0
  522. package/src/lib/room-push-dimensions.test.ts +311 -0
  523. package/src/lib/room-push-dimensions.ts +251 -0
  524. package/src/lib/room-selection-commands.ts +62 -0
  525. package/src/lib/room-selection.test.ts +1017 -0
  526. package/src/lib/room-selection.ts +417 -0
  527. package/src/lib/room-structure-commands.ts +94 -0
  528. package/src/lib/room-structure.test.ts +921 -0
  529. package/src/lib/room-transform-session.ts +570 -0
  530. package/src/lib/room-zone-routing.test.ts +294 -0
  531. package/src/lib/room-zone-routing.ts +197 -0
  532. package/src/lib/scene-clipboard.test.ts +610 -1
  533. package/src/lib/scene-clipboard.ts +269 -58
  534. package/src/lib/scene-material-refs.test.ts +79 -0
  535. package/src/lib/scene-material-refs.ts +79 -0
  536. package/src/lib/scene.test.ts +24 -36
  537. package/src/lib/scene.ts +33 -57
  538. package/src/lib/selection-routing.test.ts +129 -45
  539. package/src/lib/selection-routing.ts +54 -75
  540. package/src/lib/session-writes.test.ts +120 -0
  541. package/src/lib/session-writes.ts +205 -0
  542. package/src/lib/sfx-bus.ts +13 -0
  543. package/src/lib/sfx-player.test.ts +70 -9
  544. package/src/lib/sfx-player.ts +6 -0
  545. package/src/lib/sidebar-panel.ts +62 -0
  546. package/src/lib/site-boundary.test.ts +4 -6
  547. package/src/lib/site-boundary.ts +1 -1
  548. package/src/lib/snapping-mode.test.ts +4 -0
  549. package/src/lib/snapping-mode.ts +8 -1
  550. package/src/lib/stair-levels.test.ts +25 -0
  551. package/src/lib/stair-levels.ts +22 -3
  552. package/src/lib/terrace-draft.test.ts +145 -0
  553. package/src/lib/terrace-draft.ts +214 -0
  554. package/src/lib/tool-cursor-icon.test.ts +16 -0
  555. package/src/lib/tool-cursor-icon.ts +11 -0
  556. package/src/lib/typing-target.ts +9 -0
  557. package/src/lib/units.ts +77 -39
  558. package/src/lib/vector-edges.test.ts +58 -0
  559. package/src/lib/vector-edges.ts +400 -0
  560. package/src/lib/viewer-selection.test.ts +577 -0
  561. package/src/lib/viewer-selection.ts +580 -0
  562. package/src/lib/wall-draw-variant.test.ts +71 -0
  563. package/src/lib/wall-draw-variant.ts +91 -0
  564. package/src/lib/wall-paint-region-session.ts +246 -0
  565. package/src/lib/wall-paint-region.test.ts +373 -0
  566. package/src/lib/wall-placement-pointer.test.ts +42 -0
  567. package/src/lib/wall-placement-pointer.ts +57 -0
  568. package/src/lib/wall-polygon-draft.test.ts +262 -0
  569. package/src/lib/wall-polygon-draft.ts +286 -0
  570. package/src/lib/wall-push.test.ts +453 -0
  571. package/src/lib/wall-region-face.ts +352 -0
  572. package/src/lib/wall-region-handles.test.ts +304 -0
  573. package/src/lib/wall-region-handles.ts +368 -0
  574. package/src/lib/wall-region-list.test.ts +119 -0
  575. package/src/lib/wall-region-list.ts +136 -0
  576. package/src/lib/wall-region-snap.ts +110 -0
  577. package/src/lib/window-changes.ts +35 -0
  578. package/src/lib/window-interaction.ts +14 -0
  579. package/src/lib/zone-content.ts +4 -1
  580. package/src/lib/zone-name.test.ts +17 -0
  581. package/src/lib/zone-name.ts +17 -0
  582. package/src/store/opens-in-select-mode.test.ts +223 -0
  583. package/src/store/paint-mode.test.ts +188 -0
  584. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  585. package/src/store/tool-mode.test.ts +137 -6
  586. package/src/store/use-audio.test.ts +146 -0
  587. package/src/store/use-audio.tsx +127 -3
  588. package/src/store/use-construction-reveal.test.ts +173 -0
  589. package/src/store/use-construction-reveal.ts +145 -0
  590. package/src/store/use-corner-snap-hint.test.ts +24 -0
  591. package/src/store/use-corner-snap-hint.ts +47 -0
  592. package/src/store/use-delete-confirmation.ts +28 -1
  593. package/src/store/use-drawing-view.test.ts +24 -3
  594. package/src/store/use-drawing-view.ts +29 -1
  595. package/src/store/use-editor.tsx +432 -321
  596. package/src/store/use-floorplan-draft-preview.ts +13 -0
  597. package/src/store/use-floorplan-marquee.ts +12 -2
  598. package/src/store/use-follow-pascal.test.ts +62 -0
  599. package/src/store/use-follow-pascal.ts +53 -0
  600. package/src/store/use-point-ask.ts +247 -0
  601. package/src/store/use-quick-measurement-hud.ts +3 -3
  602. package/src/store/use-stair-build-preview.ts +33 -5
  603. package/src/store/use-wall-move-ghosts.ts +4 -2
  604. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  605. package/src/store/use-wall-region-handles-selection.ts +139 -0
  606. package/src/store/view-layout.test.ts +201 -0
  607. package/src/store/view-layout.ts +199 -0
  608. package/src/test-utils/immediate-animation-frames.ts +16 -0
  609. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  610. package/src/test-utils/render-current-markup.tsx +16 -0
  611. package/src/test-utils/selection-harness.ts +82 -0
  612. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  613. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  614. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  615. package/src/components/tools/fence/fence-drafting.ts +0 -266
  616. package/src/components/tools/stair/stair-click-guard.test.ts +0 -58
  617. package/src/components/tools/stair/stair-click-guard.ts +0 -72
  618. package/src/components/tools/stair/stair-defaults.ts +0 -18
  619. package/src/components/tools/stair/stair-tool.tsx +0 -634
  620. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  621. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  622. package/src/lib/floorplan/stairs.ts +0 -489
  623. package/src/lib/level-duplication.test.ts +0 -144
  624. package/src/lib/level-duplication.ts +0 -195
@@ -2,10 +2,13 @@
2
2
 
3
3
  import { getSceneTheme, useViewer } from '@pascal-app/viewer'
4
4
  import { type ReactNode, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
5
+ import { useShownSidebarPanel } from '../../lib/sidebar-panel'
5
6
  import useEditor from '../../store/use-editor'
6
7
  import { MobileTabBar } from '../ui/sidebar/mobile-tab-bar'
7
8
  import type { SidebarTab } from '../ui/sidebar/tab-bar'
8
9
  import { BottomSheet, type BottomSheetHandle } from './bottom-sheet'
10
+ import { useSceneSpan } from './views/scene-region'
11
+ import { ViewTabs } from './views/view-bar'
9
12
 
10
13
  const MIN_SNAP = 0
11
14
  const MAX_SNAP = 1
@@ -40,6 +43,7 @@ export interface EditorLayoutMobileProps {
40
43
  viewerToolbarRight?: ReactNode
41
44
  viewerContent: ReactNode
42
45
  overlays?: ReactNode
46
+ viewBar?: boolean
43
47
  }
44
48
 
45
49
  export function EditorLayoutMobile({
@@ -51,9 +55,11 @@ export function EditorLayoutMobile({
51
55
  viewerToolbarRight,
52
56
  viewerContent,
53
57
  overlays,
58
+ viewBar = true,
54
59
  }: EditorLayoutMobileProps) {
60
+ const hasScene = useSceneSpan() !== null
55
61
  const isCaptureMode = useEditor((s) => s.isCaptureMode)
56
- const activePanel = useEditor((s) => s.activeSidebarPanel)
62
+ const activePanel = useShownSidebarPanel(sidebarTabs)
57
63
  const setActivePanel = useEditor((s) => s.setActiveSidebarPanel)
58
64
  const panelSheetHeight = useEditor((s) => s.mobilePanelSheetHeight)
59
65
  const isDark = useViewer((s) => getSceneTheme(s.sceneTheme).appearance === 'dark')
@@ -70,30 +76,18 @@ export function EditorLayoutMobile({
70
76
 
71
77
  const currentTab = sidebarTabs.find((t) => t.id === activePanel)
72
78
 
73
- // Keep active panel valid
74
- useEffect(() => {
75
- if (sidebarTabs.length > 0 && !sidebarTabs.some((t) => t.id === activePanel)) {
76
- setActivePanel(sidebarTabs[0]!.id)
77
- }
78
- }, [sidebarTabs, activePanel, setActivePanel])
79
-
80
- // Sync editor phase / mode with the active tab:
79
+ // Sync the editor mode with the active tab:
81
80
  // - Entering Chat always drops to Select (chat is a composing context).
82
- // - Entering Items snaps the editor into furnish-build (matches the
83
- // desktop "Furnish" action which itself opens the Items panel).
84
- // - Leaving Items while still furnishing exits the build mode.
81
+ // - Leaving Items with the item tool still armed exits the build mode.
82
+ // Arming the item tool on entering Items belongs to the tap (`handleTabPress`):
83
+ // a tab restored on load opens in select mode.
85
84
  useEffect(() => {
86
- const { armToolMode, phase, mode, setPhase } = useEditor.getState()
85
+ const { armToolMode, mode, tool } = useEditor.getState()
87
86
  if (activePanel === 'ai' && mode === 'build') {
88
87
  armToolMode({ mode: 'select' })
89
88
  return
90
89
  }
91
- if (activePanel === 'items') {
92
- if (phase !== 'furnish') setPhase('furnish')
93
- if (mode !== 'build') armToolMode({ mode: 'build', tool: 'item' })
94
- return
95
- }
96
- if (phase === 'furnish' && mode === 'build') {
90
+ if (activePanel !== 'items' && mode === 'build' && tool === 'item') {
97
91
  armToolMode({ mode: 'select' })
98
92
  }
99
93
  }, [activePanel])
@@ -148,9 +142,17 @@ export function EditorLayoutMobile({
148
142
  if (middleH <= 0) return
149
143
  const tab = sidebarTabs.find((t) => t.id === id)
150
144
  if (!tab) return
145
+ if (tab.onSelect) {
146
+ tab.onSelect()
147
+ return
148
+ }
151
149
  const defaultPx = getDefaultSnap(tab) * middleH
152
150
  if (id !== activePanel) {
153
151
  setActivePanel(id)
152
+ // Tapping Items arms the item tool, as F does on desktop.
153
+ if (id === 'items' && useEditor.getState().mode !== 'build') {
154
+ useEditor.getState().armToolMode({ mode: 'build', tool: 'item' })
155
+ }
154
156
  sheetRef.current?.snapTo(defaultPx)
155
157
  return
156
158
  }
@@ -212,7 +214,7 @@ export function EditorLayoutMobile({
212
214
  }, [panelSheetHeight, committedSheetH])
213
215
 
214
216
  return (
215
- <div className="dark flex h-full w-full flex-col bg-sidebar text-foreground">
217
+ <div className="flex h-full w-full flex-col bg-sidebar text-foreground">
216
218
  {navbarSlot}
217
219
 
218
220
  <div
@@ -223,14 +225,23 @@ export function EditorLayoutMobile({
223
225
  {/* Viewer column: sized by committed sheet height */}
224
226
  <div className="absolute inset-x-0 top-0 overflow-hidden" style={{ height: viewerHeight }}>
225
227
  <div className="relative h-full w-full">
226
- {(viewerToolbarLeft || viewerToolbarRight) && !isCaptureMode && (
228
+ {(viewBar || viewerToolbarLeft || viewerToolbarRight) && !isCaptureMode && (
227
229
  <div className="pointer-events-none absolute top-3 right-3 left-3 z-20 flex items-center justify-between gap-2">
228
- <div className="pointer-events-auto flex items-center gap-2">
229
- {viewerToolbarLeft}
230
- </div>
231
- <div className="pointer-events-auto flex items-center gap-2">
232
- {viewerToolbarRight}
230
+ <div className="flex items-center gap-2">
231
+ {viewBar && (
232
+ <div data-view-bar={0}>
233
+ <ViewTabs className="shadow-elevation-4" pane={0} />
234
+ </div>
235
+ )}
236
+ <div className="pointer-events-auto flex items-center gap-2">
237
+ {viewerToolbarLeft}
238
+ </div>
233
239
  </div>
240
+ {hasScene && (
241
+ <div className="pointer-events-auto flex items-center gap-2">
242
+ {viewerToolbarRight}
243
+ </div>
244
+ )}
234
245
  </div>
235
246
  )}
236
247
  <div className="relative h-full w-full overflow-hidden">{viewerContent}</div>
@@ -2,13 +2,21 @@
2
2
 
3
3
  import { type ReactNode, useCallback, useEffect, useRef } from 'react'
4
4
  import { useIsMobile } from '../../hooks/use-mobile'
5
+ import {
6
+ openSidebarPanel,
7
+ SIDEBAR_MIN_WIDTH,
8
+ setSidebarTabIds,
9
+ showSidebarPanel,
10
+ useShownSidebarPanel,
11
+ } from '../../lib/sidebar-panel'
5
12
  import useEditor from '../../store/use-editor'
6
13
 
7
14
  import { useSidebarStore } from '../ui/primitives/sidebar'
8
15
  import { IconRail, type SidebarTab } from '../ui/sidebar/tab-bar'
9
16
  import { EditorLayoutMobile } from './editor-layout-mobile'
17
+ import { useSceneSpan } from './views/scene-region'
18
+ import { ViewBarRow } from './views/view-bar'
10
19
 
11
- const SIDEBAR_MIN_WIDTH = 300
12
20
  const SIDEBAR_MAX_WIDTH = 800
13
21
  const SIDEBAR_COLLAPSE_THRESHOLD = 220
14
22
  // Matches the `w-14` rail in <IconRail>; the resize math is relative to it.
@@ -31,27 +39,49 @@ function LeftColumn({
31
39
  const setWidth = useSidebarStore((s) => s.setWidth)
32
40
  const isDragging = useSidebarStore((s) => s.isDragging)
33
41
  const setIsDragging = useSidebarStore((s) => s.setIsDragging)
34
- const activePanel = useEditor((s) => s.activeSidebarPanel)
42
+ const activePanel = useShownSidebarPanel(tabs)
35
43
  const setActivePanel = useEditor((s) => s.setActiveSidebarPanel)
36
44
 
37
45
  const isResizing = useRef(false)
38
46
 
39
- // Ensure active panel is a valid tab
47
+ // Publish the rail's tabs so keyboard shortcuts (B, P) can open a panel.
40
48
  useEffect(() => {
41
- if (tabs.length > 0 && !tabs.some((t) => t.id === activePanel)) {
42
- setActivePanel(tabs[0]!.id)
43
- }
44
- }, [tabs, activePanel, setActivePanel])
49
+ setSidebarTabIds(tabs.map((tab) => tab.id))
50
+ return () => setSidebarTabIds([])
51
+ }, [tabs])
52
+
53
+ // A stage entry whose surface has left the stage (its view closed from the
54
+ // view bar) hands the rail back to the panel that was open before it.
55
+ const lastPanelTab = useRef<string | null>(null)
56
+ const activeTab = tabs.find((t) => t.id === activePanel)
57
+ if (activeTab && !activeTab.noPanel) lastPanelTab.current = activeTab.id
58
+ useEffect(() => {
59
+ if (activeTab?.stageActive !== false) return
60
+ const fallback = tabs.find((t) => t.id === lastPanelTab.current) ?? tabs.find((t) => !t.noPanel)
61
+ if (fallback) setActivePanel(fallback.id)
62
+ }, [activeTab, tabs, setActivePanel])
45
63
 
46
- // Leaving the items tab while furnishing should drop back to select mode
64
+ // Leaving the items tab with the item tool in hand drops back to select mode
47
65
  useEffect(() => {
48
66
  if (activePanel === 'items') return
49
- const { phase, mode, setMode } = useEditor.getState()
50
- if (phase === 'furnish' && mode === 'build') {
67
+ const { mode, tool, setMode } = useEditor.getState()
68
+ if (mode === 'build' && tool === 'item') {
51
69
  setMode('select')
52
70
  }
53
71
  }, [activePanel])
54
72
 
73
+ // Paint mode entered without the panel (command palette, a material's "paint
74
+ // with") brings the host's Paint panel along, so the rail reflects the mode.
75
+ useEffect(
76
+ () =>
77
+ useEditor.subscribe((state, prev) => {
78
+ if (state.mode !== prev.mode && state.mode === 'material-paint') {
79
+ openSidebarPanel(['paint'])
80
+ }
81
+ }),
82
+ [],
83
+ )
84
+
55
85
  // Closing (collapsing) the sidebar disarms any build tool back to select
56
86
  useEffect(() => {
57
87
  if (!isCollapsed) return
@@ -72,20 +102,20 @@ function LeftColumn({
72
102
  [setIsDragging],
73
103
  )
74
104
 
75
- // Rail click: reopen a collapsed panel, collapse when re-clicking the open
76
- // tab, otherwise switch tabs. Reopening clamps below-min persisted widths
77
- // up to the minimum so the panel always returns to a usable size.
105
+ // Rail click: reopen a collapsed panel (at a usable width), collapse when
106
+ // re-clicking the open tab, otherwise switch tabs.
78
107
  const handleRailClick = useCallback(
79
108
  (id: string) => {
109
+ const tab = tabs.find((t) => t.id === id)
110
+ if (id !== activePanel) tabs.find((t) => t.id === activePanel)?.onDeselect?.()
111
+ tab?.onSelect?.()
80
112
  // noPanel tabs drive the stage, not the panel — leave collapse state alone.
81
- if (tabs.find((t) => t.id === id)?.noPanel) {
113
+ if (tab?.noPanel) {
82
114
  setActivePanel(id)
83
115
  return
84
116
  }
85
117
  if (isCollapsed) {
86
- setIsCollapsed(false)
87
- if (width < SIDEBAR_MIN_WIDTH) setWidth(SIDEBAR_MIN_WIDTH)
88
- setActivePanel(id)
118
+ showSidebarPanel(id)
89
119
  return
90
120
  }
91
121
  if (id === activePanel) {
@@ -94,7 +124,7 @@ function LeftColumn({
94
124
  }
95
125
  setActivePanel(id)
96
126
  },
97
- [tabs, isCollapsed, width, activePanel, setIsCollapsed, setWidth, setActivePanel],
127
+ [tabs, isCollapsed, activePanel, setIsCollapsed, setActivePanel],
98
128
  )
99
129
 
100
130
  useEffect(() => {
@@ -159,59 +189,97 @@ function LeftColumn({
159
189
 
160
190
  // ── Right column: viewer area with toolbar ───────────────────────────────────
161
191
 
192
+ /**
193
+ * Host toolbars floating over the stage. The right one carries scene controls
194
+ * (wall mode, display), so it sits at the right edge of the scene panes; the
195
+ * left one floats only when there is no view bar row to carry it.
196
+ */
197
+ function StageToolbar({
198
+ toolbarLeft,
199
+ toolbarRight,
200
+ }: {
201
+ toolbarLeft?: ReactNode
202
+ toolbarRight?: ReactNode
203
+ }) {
204
+ const scene = useSceneSpan()
205
+ return (
206
+ <div className="pointer-events-none absolute top-3 right-0 left-0 z-20 h-8">
207
+ {toolbarLeft && (
208
+ <div className="pointer-events-auto absolute top-0 left-3 flex items-center gap-2">
209
+ {toolbarLeft}
210
+ </div>
211
+ )}
212
+ {toolbarRight && scene && (
213
+ <div
214
+ className="pointer-events-auto absolute top-0 flex items-center gap-2"
215
+ style={{ right: `calc(${(1 - scene.right) * 100}% + 12px)` }}
216
+ >
217
+ {toolbarRight}
218
+ </div>
219
+ )}
220
+ </div>
221
+ )
222
+ }
223
+
162
224
  function RightColumn({
163
225
  toolbarLeft,
164
226
  toolbarRight,
227
+ viewBar,
165
228
  children,
166
229
  overlays,
167
230
  stageOverlay,
168
231
  }: {
169
232
  toolbarLeft?: ReactNode
170
233
  toolbarRight?: ReactNode
234
+ viewBar: boolean
171
235
  children: ReactNode
172
236
  overlays?: ReactNode
173
237
  stageOverlay?: ReactNode
174
238
  }) {
175
239
  return (
176
- <div
177
- className="relative flex min-w-0 flex-1 flex-col overflow-hidden"
178
- style={{
179
- borderTopLeftRadius: 16,
180
- clipPath: 'inset(0 0 0 0 round 16px 0 0 0)',
181
- boxShadow: '-4px -2px 16px rgba(0, 0, 0, 0.08), -1px 0 4px rgba(0, 0, 0, 0.04)',
182
- }}
183
- >
184
- {/* Viewer toolbar */}
185
- {(toolbarLeft || toolbarRight) && (
186
- <div className="pointer-events-none absolute top-3 right-3 left-3 z-20 flex items-center justify-between gap-2">
187
- <div className="pointer-events-auto flex items-center gap-2">{toolbarLeft}</div>
188
- <div className="pointer-events-auto flex items-center gap-2">{toolbarRight}</div>
189
- </div>
190
- )}
191
- {/* Canvas area. `isolate` matters: drei's `<Html>` computes a z-index
192
- from camera distance and defaults to a range topping out at
193
- 16,777,271, and without a stacking context here those values compete
194
- directly with the viewer toolbar (z-20), the stage overlay (z-10) and
195
- the overlay band (z-30) — so an in-scene tool badge painted over all
196
- three. Isolating pins every in-scene HTML layer inside the canvas,
197
- where it belongs, and leaves their order relative to each other
198
- untouched. */}
199
- <div className="relative isolate flex-1 overflow-hidden">{children}</div>
200
- {/* Stage overlay — replaces the canvas visually (e.g. studio gallery)
201
- while keeping it mounted. Sits below the viewer toolbar (z-20) so
202
- the stage switch stays reachable. */}
203
- {stageOverlay && <div className="absolute inset-0 z-10">{stageOverlay}</div>}
204
- {/* Overlays scoped to the viewer column. `data-viewer-bounds` marks the
205
- draggable region the floating inspector clamps itself to. */}
206
- {overlays && (
207
- <div
208
- className="pointer-events-none absolute inset-0 z-30"
209
- data-viewer-bounds
210
- style={{ transform: 'translateZ(0)' }}
211
- >
212
- {overlays}
213
- </div>
214
- )}
240
+ <div className="relative flex min-w-0 flex-1 flex-col overflow-hidden">
241
+ {/* The view bar row sits above the stage, one bar per pane; the host's
242
+ left toolbar rides after the first pane's views. */}
243
+ {viewBar && <ViewBarRow leading={toolbarLeft} />}
244
+ <div
245
+ className="relative flex min-h-0 flex-1 flex-col overflow-hidden"
246
+ style={{
247
+ borderTopLeftRadius: 16,
248
+ clipPath: 'inset(0 0 0 0 round 16px 0 0 0)',
249
+ boxShadow: '-4px -2px 16px rgba(0, 0, 0, 0.08), -1px 0 4px rgba(0, 0, 0, 0.04)',
250
+ }}
251
+ >
252
+ {((!viewBar && toolbarLeft) || toolbarRight) && (
253
+ <StageToolbar
254
+ toolbarLeft={viewBar ? undefined : toolbarLeft}
255
+ toolbarRight={toolbarRight}
256
+ />
257
+ )}
258
+ {/* Canvas area. `isolate` matters: drei's `<Html>` computes a z-index
259
+ from camera distance and defaults to a range topping out at
260
+ 16,777,271, and without a stacking context here those values compete
261
+ directly with the viewer toolbar (z-20), the stage overlay (z-10) and
262
+ the overlay band (z-30) — so an in-scene tool badge painted over all
263
+ three. Isolating pins every in-scene HTML layer inside the canvas,
264
+ where it belongs, and leaves their order relative to each other
265
+ untouched. */}
266
+ <div className="relative isolate flex-1 overflow-hidden">{children}</div>
267
+ {/* Stage overlay — replaces the whole stage visually (e.g. the item
268
+ builder) while keeping the canvas mounted. The view bar row steps
269
+ aside for it (see `EditorLayoutV2`); the host's toolbars stay above. */}
270
+ {stageOverlay && <div className="absolute inset-0 z-10">{stageOverlay}</div>}
271
+ {/* Overlays scoped to the stage. `data-viewer-bounds` marks the
272
+ draggable region the floating inspector clamps itself to. */}
273
+ {overlays && (
274
+ <div
275
+ className="pointer-events-none absolute inset-0 z-30"
276
+ data-viewer-bounds
277
+ style={{ transform: 'translateZ(0)' }}
278
+ >
279
+ {overlays}
280
+ </div>
281
+ )}
282
+ </div>
215
283
  </div>
216
284
  )
217
285
  }
@@ -228,6 +296,8 @@ export interface EditorLayoutV2Props {
228
296
  viewerContent: ReactNode
229
297
  overlays?: ReactNode
230
298
  stageOverlay?: ReactNode
299
+ /** Show each pane's view bar. Off for hosts that present one fixed view. */
300
+ viewBar?: boolean
231
301
  }
232
302
 
233
303
  export function EditorLayoutV2({
@@ -240,6 +310,7 @@ export function EditorLayoutV2({
240
310
  viewerContent,
241
311
  overlays,
242
312
  stageOverlay,
313
+ viewBar = true,
243
314
  }: EditorLayoutV2Props) {
244
315
  const isCaptureMode = useEditor((s) => s.isCaptureMode)
245
316
  const isMobile = useIsMobile()
@@ -251,8 +322,9 @@ export function EditorLayoutV2({
251
322
  overlays={overlays}
252
323
  renderTabContent={renderTabContent}
253
324
  sidebarOverlay={sidebarOverlay}
254
- sidebarTabs={sidebarTabs.filter((t) => !t.noPanel)}
325
+ sidebarTabs={sidebarTabs.filter((t) => !t.noPanel || t.onSelect)}
255
326
  viewerContent={viewerContent}
327
+ viewBar={viewBar}
256
328
  viewerToolbarLeft={viewerToolbarLeft}
257
329
  viewerToolbarRight={viewerToolbarRight}
258
330
  />
@@ -260,7 +332,7 @@ export function EditorLayoutV2({
260
332
  }
261
333
 
262
334
  return (
263
- <div className="dark flex h-full w-full flex-col bg-sidebar text-foreground">
335
+ <div className="flex h-full w-full flex-col bg-sidebar text-foreground">
264
336
  {/* Top navbar */}
265
337
  {navbarSlot}
266
338
 
@@ -278,6 +350,7 @@ export function EditorLayoutV2({
278
350
  stageOverlay={stageOverlay}
279
351
  toolbarLeft={isCaptureMode ? undefined : viewerToolbarLeft}
280
352
  toolbarRight={isCaptureMode ? undefined : viewerToolbarRight}
353
+ viewBar={viewBar && !isCaptureMode && !stageOverlay}
281
354
  >
282
355
  {viewerContent}
283
356
  </RightColumn>
@@ -1,13 +1,19 @@
1
1
  'use client'
2
2
 
3
- import { emitter, useScene } from '@pascal-app/core'
3
+ import { emitter, getProjectFileSink, type ProjectFileSink } from '@pascal-app/core'
4
4
  import { disposeObject3DResources, snapLevelsToTruePositions, useViewer } from '@pascal-app/viewer'
5
5
  import { useThree } from '@react-three/fiber'
6
6
  import { useEffect } from 'react'
7
7
  import * as THREE from 'three'
8
8
  import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js'
9
9
  import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js'
10
- import { exportSceneToGlb, nextFrames, prepareSceneForExport } from '../../lib/glb-export'
10
+ import { downloadProjectFile } from '../../lib/download-project-file'
11
+ import {
12
+ exportSceneToGlb,
13
+ prepareSceneForExport,
14
+ waitForExportGeometry,
15
+ } from '../../lib/glb-export'
16
+ import { exportPreparedSceneToIfc, ifcFileName } from '../../lib/ifc-export'
11
17
  import { exportSceneLevelsForPrint } from '../../lib/level-print-export'
12
18
  import type { ModelExport, ModelExportArtifact } from '../../lib/model-export'
13
19
  import {
@@ -20,6 +26,7 @@ import { filterPreparedSceneForPrintContent } from '../../lib/print-content-scop
20
26
  import { exportSceneToPrintStl, mergePrintExportDiagnostics } from '../../lib/print-export'
21
27
  import { applySemanticPrintFeatureThickness } from '../../lib/print-feature-thickness'
22
28
  import { compileSemanticPrintShellWithManifold } from '../../lib/print-shell-compiler-manifold-worker'
29
+ import { getSavedSceneDocument } from '../../lib/scene'
23
30
  import { exportSceneToUsdz } from '../../lib/usdz-export'
24
31
  import useEditor from '../../store/use-editor'
25
32
 
@@ -51,7 +58,11 @@ export function ExportManager() {
51
58
  const setModelExport = useEditor((state) => state.setModelExport)
52
59
 
53
60
  useEffect(() => {
54
- const exportFn: ModelExport = async (format = 'glb', options = {}) => {
61
+ const exportFn: ModelExport = async (format = 'glb', requested = {}) => {
62
+ const finishArtifact = artifactFinisher(getProjectFileSink())
63
+ // Resolve once: scene preparation prunes hidden nodes by default, and the
64
+ // IFC writer must drop the same nodes it rebuilds parametrically.
65
+ const options = { ...requested, onlyVisible: requested.onlyVisible ?? true }
55
66
  // Find the scene renderer group by name
56
67
  const sceneGroup = scene.getObjectByName('scene-renderer')
57
68
  if (!sceneGroup) {
@@ -67,11 +78,12 @@ export function ExportManager() {
67
78
  // without it every plant exports as its raycast collider, a white box).
68
79
  useViewer.getState().setExporting(true)
69
80
  try {
70
- await nextFrames()
81
+ await waitForExportGeometry(getSavedSceneDocument().nodes, options)
82
+ const { nodes, collections } = getSavedSceneDocument()
71
83
 
72
84
  if (format === 'glb') {
73
85
  const warnings: string[] = []
74
- const buffer = await exportSceneToGlb(sceneGroup, useScene.getState().nodes, {
86
+ const buffer = await exportSceneToGlb(sceneGroup, nodes, {
75
87
  ...options,
76
88
  onWarning: (warning) => warnings.push(warning),
77
89
  })
@@ -81,7 +93,7 @@ export function ExportManager() {
81
93
 
82
94
  if (format === 'usdz') {
83
95
  const warnings: string[] = []
84
- const data = await exportSceneToUsdz(sceneGroup, useScene.getState().nodes, {
96
+ const data = await exportSceneToUsdz(sceneGroup, nodes, {
85
97
  ...options,
86
98
  onWarning: (warning) => warnings.push(warning),
87
99
  })
@@ -95,12 +107,11 @@ export function ExportManager() {
95
107
  // window, so the export snapshots the clean building, then restore.
96
108
  emitter.emit('thumbnail:before-capture', undefined)
97
109
  const restoreLevels = snapLevelsToTruePositions()
98
- const nodes = useScene.getState().nodes
99
110
  let prepared: ReturnType<typeof prepareSceneForExport>
100
111
  try {
101
112
  prepared = prepareSceneForExport(sceneGroup, nodes, {
102
113
  ...options,
103
- requireSynchronousBake: format === 'stl' || format === 'obj',
114
+ requireSynchronousBake: format === 'stl' || format === 'obj' || format === 'ifc',
104
115
  })
105
116
  } finally {
106
117
  restoreLevels()
@@ -206,6 +217,23 @@ export function ExportManager() {
206
217
  return finishArtifact(blob, `model_${date}.stl`, options.download)
207
218
  }
208
219
 
220
+ if (format === 'ifc') {
221
+ const { data, warnings } = exportPreparedSceneToIfc(exportScene, nodes, {
222
+ collections,
223
+ projectName: options.projectName,
224
+ onlyVisible: options.onlyVisible,
225
+ excludedNodeTypes: options.excludedNodeTypes,
226
+ })
227
+ const blob = new Blob([data], { type: 'application/x-step' })
228
+ return finishArtifact(
229
+ blob,
230
+ ifcFileName(options.projectName, `model_${date}`),
231
+ options.download,
232
+ undefined,
233
+ warnings,
234
+ )
235
+ }
236
+
209
237
  if (format === 'obj') {
210
238
  const exporter = new OBJExporter()
211
239
  const result = exporter.parse(exportScene)
@@ -236,22 +264,15 @@ export function ExportManager() {
236
264
  return null
237
265
  }
238
266
 
239
- function finishArtifact(
240
- blob: Blob,
241
- filename: string,
242
- download: boolean | undefined,
243
- metadata?: unknown,
244
- warnings?: readonly string[],
245
- ): ModelExportArtifact {
246
- if (download !== false) downloadBlob(blob, filename)
247
- return { blob, filename, metadata, warnings: warnings?.length ? warnings : undefined }
248
- }
249
-
250
- function downloadBlob(blob: Blob, filename: string) {
251
- const url = URL.createObjectURL(blob)
252
- const link = document.createElement('a')
253
- link.href = url
254
- link.download = filename
255
- link.click()
256
- URL.revokeObjectURL(url)
267
+ function artifactFinisher(sink: ProjectFileSink | null) {
268
+ return async (
269
+ blob: Blob,
270
+ filename: string,
271
+ download: boolean | undefined,
272
+ metadata?: unknown,
273
+ warnings?: readonly string[],
274
+ ): Promise<ModelExportArtifact> => {
275
+ if (download !== false) await downloadProjectFile(blob, filename, sink)
276
+ return { blob, filename, metadata, warnings: warnings?.length ? warnings : undefined }
277
+ }
257
278
  }