@pascal-app/editor 0.9.1 → 1.0.0-beta.1

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 (366) hide show
  1. package/package.json +14 -9
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +10 -2
  3. package/src/components/editor/bake-exporter.tsx +49 -0
  4. package/src/components/editor/bake-thumbnail.tsx +95 -0
  5. package/src/components/editor/camera-dragging-lifecycle.test.ts +49 -0
  6. package/src/components/editor/camera-dragging-lifecycle.ts +41 -0
  7. package/src/components/editor/custom-camera-controls.tsx +332 -199
  8. package/src/components/editor/delete-confirmation-dialog.tsx +54 -0
  9. package/src/components/editor/editor-layout-mobile.tsx +5 -0
  10. package/src/components/editor/editor-layout-v2.tsx +18 -0
  11. package/src/components/editor/elevation-3d-guide-layer.tsx +120 -0
  12. package/src/components/editor/export-manager.tsx +71 -63
  13. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  14. package/src/components/editor/first-person/build-collider-world.test.ts +102 -5
  15. package/src/components/editor/first-person/build-collider-world.ts +52 -5
  16. package/src/components/editor/first-person/terrain-collider.test.ts +132 -0
  17. package/src/components/editor/first-person/terrain-collider.ts +212 -0
  18. package/src/components/editor/first-person-controls.tsx +410 -136
  19. package/src/components/editor/floating-action-menu.tsx +435 -64
  20. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  21. package/src/components/editor/floorplan-background-selection.test.ts +72 -0
  22. package/src/components/editor/floorplan-background-selection.ts +7 -20
  23. package/src/components/editor/floorplan-camera-sync.test.ts +249 -0
  24. package/src/components/editor/floorplan-camera-sync.ts +213 -0
  25. package/src/components/editor/floorplan-mode-coordinator.tsx +82 -0
  26. package/src/components/editor/floorplan-navigation-presentation.test.ts +142 -0
  27. package/src/components/editor/floorplan-navigation-presentation.ts +156 -0
  28. package/src/components/editor/floorplan-panel.tsx +2517 -1492
  29. package/src/components/editor/grid.tsx +166 -39
  30. package/src/components/editor/group-actions.ts +597 -0
  31. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  32. package/src/components/editor/group-move-3d.ts +397 -0
  33. package/src/components/editor/group-rotate-handle.tsx +102 -64
  34. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  35. package/src/components/editor/group-transform-shared.test.ts +192 -1
  36. package/src/components/editor/group-transform-shared.ts +240 -7
  37. package/src/components/editor/handles/handle-arrow.tsx +81 -23
  38. package/src/components/editor/handles/handle-drag-history.test.ts +96 -0
  39. package/src/components/editor/handles/handle-drag-history.ts +14 -0
  40. package/src/components/editor/handles/preview-overrides.test.ts +26 -0
  41. package/src/components/editor/handles/preview-overrides.ts +13 -0
  42. package/src/components/editor/handles/resize-snap.test.ts +52 -0
  43. package/src/components/editor/handles/resize-snap.ts +23 -0
  44. package/src/components/editor/handles/use-handle-drag.ts +49 -6
  45. package/src/components/editor/index.tsx +289 -35
  46. package/src/components/editor/measurement-pill.tsx +68 -23
  47. package/src/components/editor/node-action-menu.tsx +14 -1
  48. package/src/components/editor/node-arrow-handles.tsx +409 -180
  49. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  50. package/src/components/editor/quick-measurement-card.tsx +76 -0
  51. package/src/components/editor/quick-measurement-hud.tsx +34 -0
  52. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  53. package/src/components/editor/selection-manager.tsx +814 -466
  54. package/src/components/editor/site-edge-labels.tsx +28 -6
  55. package/src/components/editor/slab-hole-highlights.tsx +13 -5
  56. package/src/components/editor/snapshot-capture-overlay.tsx +258 -115
  57. package/src/components/editor/three-context-bridge.ts +22 -0
  58. package/src/components/editor/thumbnail-generator.tsx +118 -253
  59. package/src/components/editor/use-floorplan-background-placement.ts +113 -45
  60. package/src/components/editor/use-floorplan-hit-testing.test.ts +82 -0
  61. package/src/components/editor/use-floorplan-hit-testing.ts +144 -161
  62. package/src/components/editor/use-floorplan-scene-data.ts +5 -5
  63. package/src/components/editor/use-mesh-settle-epoch.ts +27 -0
  64. package/src/components/editor/wall-measurement-label.tsx +10 -28
  65. package/src/components/editor/wall-move-side-handles.tsx +229 -39
  66. package/src/components/editor/wall-opening-highlights.tsx +63 -32
  67. package/src/components/editor/wall-snap-beacon-layer.tsx +162 -6
  68. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +9 -5
  69. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +2 -1
  70. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +40 -9
  71. package/src/components/editor-2d/floorplan-cursor-indicator-position.test.ts +31 -0
  72. package/src/components/editor-2d/floorplan-cursor-indicator-position.ts +39 -0
  73. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  74. package/src/components/editor-2d/floorplan-group-move.tsx +744 -0
  75. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  76. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1672 -0
  77. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  78. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +72 -0
  79. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +219 -20
  80. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +327 -59
  81. package/src/components/editor-2d/floorplan-render-context.test.ts +27 -0
  82. package/src/components/editor-2d/floorplan-render-context.tsx +113 -8
  83. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +5 -3
  84. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +285 -0
  85. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +516 -0
  86. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +236 -0
  87. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +618 -0
  88. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +369 -0
  89. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +431 -32
  90. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +89 -0
  91. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +106 -0
  92. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +94 -26
  93. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +587 -0
  94. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +2280 -578
  95. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +20 -10
  96. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  97. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +425 -61
  98. package/src/components/systems/ceiling/ceiling-system.tsx +97 -4
  99. package/src/components/systems/roof/roof-edit-system.tsx +1768 -13
  100. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  101. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  102. package/src/components/systems/zone/zone-system.tsx +42 -13
  103. package/src/components/tools/elevator/elevator-tool.tsx +34 -8
  104. package/src/components/tools/elevator/move-elevator-tool.tsx +13 -2
  105. package/src/components/tools/fence/fence-drafting.ts +107 -8
  106. package/src/components/tools/item/move-tool.tsx +17 -13
  107. package/src/components/tools/item/placement-math.test.ts +13 -1
  108. package/src/components/tools/item/placement-math.ts +28 -2
  109. package/src/components/tools/item/placement-strategies.ts +246 -5
  110. package/src/components/tools/item/placement-types.ts +13 -1
  111. package/src/components/tools/item/use-draft-node.ts +146 -57
  112. package/src/components/tools/item/use-placement-coordinator.tsx +813 -125
  113. package/src/components/tools/registry/move-registry-node-tool.tsx +636 -103
  114. package/src/components/tools/roof/roof-tool.tsx +336 -94
  115. package/src/components/tools/select/box-select-state.ts +18 -7
  116. package/src/components/tools/select/box-select-tool.tsx +176 -40
  117. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  118. package/src/components/tools/select/marquee-geometry.ts +155 -0
  119. package/src/components/tools/select/plane-box-select-tool.tsx +1 -8
  120. package/src/components/tools/select/select-candidates.ts +1 -1
  121. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  122. package/src/components/tools/shared/drag-bounding-box.tsx +28 -4
  123. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  124. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  125. package/src/components/tools/shared/floor-stack-preview.ts +4 -0
  126. package/src/components/tools/shared/horizontal-construction-plane.ts +123 -0
  127. package/src/components/tools/shared/placement-box.tsx +183 -1
  128. package/src/components/tools/shared/pointer-support-cap.test.ts +98 -0
  129. package/src/components/tools/shared/pointer-support-cap.ts +273 -0
  130. package/src/components/tools/shared/polygon-editor.tsx +276 -35
  131. package/src/components/tools/site/site-boundary-editor.tsx +101 -42
  132. package/src/components/tools/site/stroke-pointer-ownership.test.ts +91 -0
  133. package/src/components/tools/site/stroke-pointer-ownership.ts +57 -0
  134. package/src/components/tools/site/terrain-brush-cursor.tsx +247 -0
  135. package/src/components/tools/site/terrain-sculpt-grid.tsx +192 -0
  136. package/src/components/tools/site/terrain-sculpt-tool.tsx +356 -0
  137. package/src/components/tools/stair/stair-click-guard.test.ts +58 -0
  138. package/src/components/tools/stair/stair-click-guard.ts +72 -0
  139. package/src/components/tools/stair/stair-defaults.ts +1 -0
  140. package/src/components/tools/stair/stair-tool.tsx +164 -42
  141. package/src/components/tools/tool-manager.tsx +149 -30
  142. package/src/components/tools/wall/wall-drafting.test.ts +447 -0
  143. package/src/components/tools/wall/wall-drafting.ts +304 -108
  144. package/src/components/tools/wall/wall-snap-geometry.test.ts +52 -0
  145. package/src/components/tools/wall/wall-snap-geometry.ts +137 -9
  146. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  147. package/src/components/tools/zone/zone-tool.tsx +98 -88
  148. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  149. package/src/components/ui/action-menu/control-modes.tsx +44 -94
  150. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  151. package/src/components/ui/action-menu/index.tsx +2 -4
  152. package/src/components/ui/action-menu/measurement-control.tsx +267 -0
  153. package/src/components/ui/action-menu/structure-tools.tsx +22 -13
  154. package/src/components/ui/action-menu/view-toggles.tsx +42 -90
  155. package/src/components/ui/command-palette/editor-commands.tsx +16 -3
  156. package/src/components/ui/command-palette/index.tsx +4 -3
  157. package/src/components/ui/controls/material-paint-panel.tsx +93 -18
  158. package/src/components/ui/controls/material-picker.tsx +162 -152
  159. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  160. package/src/components/ui/controls/metric-control.tsx +118 -44
  161. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  162. package/src/components/ui/controls/slider-control.tsx +88 -38
  163. package/src/components/ui/controls/terrain-sculpt-panel.tsx +193 -0
  164. package/src/components/ui/floating-level-selector.tsx +74 -20
  165. package/src/components/ui/helpers/building-helper.tsx +10 -23
  166. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  167. package/src/components/ui/helpers/helper-manager.tsx +273 -14
  168. package/src/components/ui/helpers/item-helper.tsx +28 -32
  169. package/src/components/ui/helpers/registered-tool-helper.tsx +45 -11
  170. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  171. package/src/components/ui/icon-ref.tsx +47 -0
  172. package/src/components/ui/item-catalog/catalog-items.tsx +39 -1
  173. package/src/components/ui/item-catalog/item-catalog.tsx +13 -36
  174. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  175. package/src/components/ui/panels/multi-selection-panel.tsx +57 -0
  176. package/src/components/ui/panels/node-display.ts +17 -17
  177. package/src/components/ui/panels/panel-manager.tsx +36 -14
  178. package/src/components/ui/panels/panel-wrapper.tsx +24 -3
  179. package/src/components/ui/panels/parametric-inspector.tsx +19 -4
  180. package/src/components/ui/panels/reference-panel.tsx +54 -19
  181. package/src/components/ui/panels/selection-breakdown.test.ts +24 -0
  182. package/src/components/ui/panels/selection-breakdown.ts +20 -0
  183. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  184. package/src/components/ui/primitives/sidebar.tsx +1 -0
  185. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  186. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  187. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  188. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +82 -41
  189. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +76 -6
  190. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +12 -4
  191. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +2 -1
  192. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -3
  193. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +10 -7
  194. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  195. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  196. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +10 -7
  197. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -1
  198. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +1 -1
  199. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +10 -7
  200. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -17
  201. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  202. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  203. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +72 -25
  204. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
  205. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +12 -8
  206. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +4 -3
  207. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +10 -7
  208. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -1
  209. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  210. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +44 -3
  211. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  212. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  213. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  214. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  215. package/src/components/ui/sidebar/tab-bar.tsx +42 -28
  216. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  217. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  218. package/src/components/ui/snap-target-badge.tsx +88 -0
  219. package/src/components/viewer/viewer-controls-bar.tsx +466 -0
  220. package/src/components/viewer/viewer-scene-header.tsx +235 -0
  221. package/src/components/viewer-overlay.tsx +36 -596
  222. package/src/components/viewer-zone-system.tsx +6 -1
  223. package/src/components/walkthrough-hud.tsx +111 -0
  224. package/src/hooks/use-auto-save.test.ts +14 -0
  225. package/src/hooks/use-auto-save.ts +54 -7
  226. package/src/hooks/use-ceiling-events.ts +3 -2
  227. package/src/hooks/use-drag-action.ts +4 -1
  228. package/src/hooks/use-grid-events.ts +24 -0
  229. package/src/hooks/use-keyboard.ts +438 -70
  230. package/src/index.tsx +355 -9
  231. package/src/lib/active-placement-surface.test.ts +24 -0
  232. package/src/lib/active-placement-surface.ts +42 -0
  233. package/src/lib/camera-pose.test.ts +237 -0
  234. package/src/lib/camera-pose.ts +172 -0
  235. package/src/lib/ceiling-plan-snap.ts +24 -0
  236. package/src/lib/contextual-help.test.ts +112 -0
  237. package/src/lib/contextual-help.ts +144 -0
  238. package/src/lib/continuation.ts +64 -0
  239. package/src/lib/direct-manipulation.test.ts +275 -0
  240. package/src/lib/direct-manipulation.ts +118 -0
  241. package/src/lib/door-interaction.ts +1 -1
  242. package/src/lib/editor-api.ts +23 -35
  243. package/src/lib/elevation-guides.test.ts +107 -0
  244. package/src/lib/elevation-guides.ts +236 -0
  245. package/src/lib/floorplan/annotation-visibility.test.ts +327 -0
  246. package/src/lib/floorplan/annotation-visibility.ts +133 -0
  247. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  248. package/src/lib/floorplan/apply-alignment.ts +18 -12
  249. package/src/lib/floorplan/drawing-coordination.test.ts +46 -0
  250. package/src/lib/floorplan/drawing-coordination.ts +13 -0
  251. package/src/lib/floorplan/floorplan-export.test.ts +319 -0
  252. package/src/lib/floorplan/floorplan-export.tsx +978 -0
  253. package/src/lib/floorplan/floorplan-extension.test.ts +43 -0
  254. package/src/lib/floorplan/floorplan-extension.ts +181 -0
  255. package/src/lib/floorplan/floorplan-mode.test.ts +120 -0
  256. package/src/lib/floorplan/floorplan-mode.ts +73 -0
  257. package/src/lib/floorplan/floorplan-pdfkit-document.ts +194 -0
  258. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +107 -0
  259. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +680 -0
  260. package/src/lib/floorplan/geometry.ts +52 -0
  261. package/src/lib/floorplan/index.ts +3 -0
  262. package/src/lib/floorplan/items.ts +5 -2
  263. package/src/lib/floorplan/plan-coords.ts +21 -0
  264. package/src/lib/fresh-planar-placement.test.ts +283 -3
  265. package/src/lib/fresh-planar-placement.ts +85 -1
  266. package/src/lib/glb-export.test.ts +556 -0
  267. package/src/lib/glb-export.ts +1071 -0
  268. package/src/lib/ground-surface.ts +95 -0
  269. package/src/lib/history.test.ts +96 -0
  270. package/src/lib/history.ts +31 -0
  271. package/src/lib/interaction/hot-set.test.ts +133 -0
  272. package/src/lib/interaction/hot-set.ts +67 -0
  273. package/src/lib/interaction/overlay-policy.test.ts +52 -0
  274. package/src/lib/interaction/overlay-policy.ts +59 -0
  275. package/src/lib/interaction/scope.ts +243 -0
  276. package/src/lib/level-duplication.test.ts +2 -1
  277. package/src/lib/level-selection.ts +2 -2
  278. package/src/lib/material-paint.ts +36 -49
  279. package/src/lib/measurement-kind.test.ts +30 -0
  280. package/src/lib/measurement-kind.ts +19 -0
  281. package/src/lib/measurement-label.test.ts +47 -0
  282. package/src/lib/measurement-label.ts +68 -0
  283. package/src/lib/measurement-parser.ts +116 -0
  284. package/src/lib/measurements.test.ts +188 -0
  285. package/src/lib/measurements.ts +210 -0
  286. package/src/lib/paint-scope.test.ts +454 -0
  287. package/src/lib/paint-scope.ts +461 -0
  288. package/src/lib/placement-drag-release.ts +23 -0
  289. package/src/lib/planar-cursor-placement.test.ts +57 -0
  290. package/src/lib/plugin-panels.test.ts +19 -0
  291. package/src/lib/plugin-panels.ts +91 -0
  292. package/src/lib/quick-action-feedback.ts +33 -0
  293. package/src/lib/quick-action-nodes.test.ts +94 -0
  294. package/src/lib/quick-action-nodes.ts +47 -0
  295. package/src/lib/quick-measurement.test.ts +77 -0
  296. package/src/lib/quick-measurement.ts +109 -0
  297. package/src/lib/roof-duplication.ts +6 -6
  298. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  299. package/src/lib/roof-wall-hit.ts +164 -0
  300. package/src/lib/scene-clipboard.test.ts +325 -0
  301. package/src/lib/scene-clipboard.ts +310 -21
  302. package/src/lib/scene.ts +28 -5
  303. package/src/lib/selection-routing.test.ts +313 -0
  304. package/src/lib/selection-routing.ts +180 -0
  305. package/src/lib/sfx/index.ts +1 -0
  306. package/src/lib/sfx/movement-tick.test.ts +65 -0
  307. package/src/lib/sfx/movement-tick.ts +18 -0
  308. package/src/lib/sfx-bus.ts +82 -14
  309. package/src/lib/sfx-player.test.ts +266 -0
  310. package/src/lib/sfx-player.ts +262 -69
  311. package/src/lib/site-boundary.test.ts +53 -0
  312. package/src/lib/site-boundary.ts +27 -0
  313. package/src/lib/slab-plan-snap.test.ts +93 -0
  314. package/src/lib/slab-plan-snap.ts +108 -0
  315. package/src/lib/snapping-mode.test.ts +165 -0
  316. package/src/lib/snapping-mode.ts +191 -0
  317. package/src/lib/stair-duplication.test.ts +103 -0
  318. package/src/lib/stair-duplication.ts +58 -86
  319. package/src/lib/stair-levels.ts +2 -0
  320. package/src/lib/surface-plan-snap.test.ts +71 -0
  321. package/src/lib/surface-plan-snap.ts +256 -0
  322. package/src/lib/terrain-sculpt.test.ts +429 -0
  323. package/src/lib/terrain-sculpt.ts +301 -0
  324. package/src/lib/terrain-verb-color.test.ts +51 -0
  325. package/src/lib/terrain-verb-color.ts +58 -0
  326. package/src/lib/touch-gesture-priority.test.ts +45 -0
  327. package/src/lib/touch-gesture-priority.ts +30 -0
  328. package/src/lib/use-linear-display.ts +40 -0
  329. package/src/lib/window-interaction.ts +1 -1
  330. package/src/lib/world-grid-snap.ts +37 -5
  331. package/src/store/camera-pose-store.test.ts +29 -0
  332. package/src/store/camera-pose-store.ts +27 -0
  333. package/src/store/live-draft-preview-stores.test.ts +136 -0
  334. package/src/store/navigation-sync-pose-store.test.ts +34 -0
  335. package/src/store/navigation-sync-pose-store.ts +27 -0
  336. package/src/store/terrain-sculpt-mode.test.ts +223 -0
  337. package/src/store/use-delete-confirmation.ts +27 -0
  338. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  339. package/src/store/use-drawing-view.test.ts +23 -0
  340. package/src/store/use-drawing-view.ts +84 -0
  341. package/src/store/use-editor.tsx +665 -126
  342. package/src/store/use-elevation-guides.ts +40 -0
  343. package/src/store/use-facing-pose.ts +44 -0
  344. package/src/store/use-fence-curve-draft.ts +51 -0
  345. package/src/store/use-first-person-hud.ts +40 -0
  346. package/src/store/use-floorplan-annotation-visibility.ts +60 -0
  347. package/src/store/use-floorplan-draft-preview.ts +151 -0
  348. package/src/store/use-floorplan-marquee.ts +50 -0
  349. package/src/store/use-floorplan-mode.ts +110 -0
  350. package/src/store/use-interaction-scope.test.ts +206 -0
  351. package/src/store/use-interaction-scope.ts +140 -0
  352. package/src/store/use-measurement-draft.test.ts +559 -0
  353. package/src/store/use-measurement-draft.ts +570 -0
  354. package/src/store/use-opening-guides.ts +41 -0
  355. package/src/store/use-path-draft-preview.ts +90 -0
  356. package/src/store/use-placement-preview.ts +11 -3
  357. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  358. package/src/store/use-quick-measurement-hud.ts +77 -0
  359. package/src/store/use-segment-draft-chain.ts +28 -0
  360. package/src/store/use-stair-build-preview.ts +53 -0
  361. package/src/store/use-wall-snap-indicator.ts +2 -0
  362. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  363. package/src/components/editor/group-move-handle.tsx +0 -316
  364. package/src/components/ui/panels/paint-panel.tsx +0 -163
  365. package/src/lib/floorplan/selection-tool.ts +0 -271
  366. package/src/lib/level-name.ts +0 -11
@@ -0,0 +1,111 @@
1
+ 'use client'
2
+
3
+ import type { ReactNode } from 'react'
4
+ import { cn } from '../lib/utils'
5
+ import type { WalkthroughInteract } from '../store/use-first-person-hud'
6
+
7
+ export type { WalkthroughInteract } from '../store/use-first-person-hud'
8
+
9
+ export type WalkthroughHudProps = {
10
+ floorLabel?: string | null
11
+ zoneLabel?: string | null
12
+ interact?: WalkthroughInteract
13
+ /** Pointer lock temporarily released (OS screenshot) — the pill flips to
14
+ * "Click to resume" and lets clicks fall through to the canvas. */
15
+ suspended?: boolean
16
+ onExit?: () => void
17
+ children?: ReactNode
18
+ }
19
+
20
+ export function WalkthroughHud({
21
+ floorLabel,
22
+ zoneLabel,
23
+ interact = null,
24
+ suspended = false,
25
+ onExit,
26
+ children,
27
+ }: WalkthroughHudProps) {
28
+ const kbdClass = 'rounded border border-border/60 bg-white/10 px-1.5 py-0.5 font-mono text-[10px]'
29
+ const pillClass =
30
+ 'flex items-center gap-1.5 rounded-full border border-border/40 bg-background/70 px-3 py-1 text-muted-foreground text-xs backdrop-blur-xl'
31
+ const exitContent = (
32
+ <>
33
+ <kbd className={kbdClass}>Esc</kbd>
34
+ to exit
35
+ </>
36
+ )
37
+
38
+ return (
39
+ <div className="dark pointer-events-none absolute inset-0 z-30 text-foreground">
40
+ <div className="absolute top-6 left-1/2 flex -translate-x-1/2 flex-col items-center gap-1.5">
41
+ {floorLabel && (
42
+ <div className="font-medium text-[10px] text-muted-foreground uppercase tracking-wider">
43
+ {floorLabel}
44
+ </div>
45
+ )}
46
+ {zoneLabel && (
47
+ <div className="corner-smooth rounded-full border border-border/40 bg-background/80 px-3 py-1 font-medium text-sm shadow-elevation-3 backdrop-blur-xl">
48
+ {zoneLabel}
49
+ </div>
50
+ )}
51
+ {children}
52
+ </div>
53
+
54
+ <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
55
+ <div
56
+ className={cn(
57
+ 'rounded-full transition-all duration-150',
58
+ interact
59
+ ? 'h-4 w-4 border-2 border-emerald-400 bg-emerald-400/10'
60
+ : 'h-1.5 w-1.5 bg-white/80 shadow-[0_0_2px_rgba(0,0,0,0.6)]',
61
+ )}
62
+ />
63
+ </div>
64
+
65
+ <div className="absolute bottom-6 left-1/2 flex -translate-x-1/2 items-center gap-2">
66
+ {suspended ? (
67
+ <div className={pillClass}>
68
+ <span className="font-medium text-foreground">Click</span>
69
+ <span>or</span>
70
+ <kbd className={kbdClass}>P</kbd>
71
+ <span>to resume</span>
72
+ <span className="text-muted-foreground/60">·</span>
73
+ {exitContent}
74
+ </div>
75
+ ) : (
76
+ <>
77
+ <div className={pillClass}>
78
+ <kbd className={kbdClass}>P</kbd>
79
+ free cursor
80
+ </div>
81
+ {onExit ? (
82
+ <button
83
+ className={cn(pillClass, 'pointer-events-auto')}
84
+ onClick={onExit}
85
+ type="button"
86
+ >
87
+ {exitContent}
88
+ </button>
89
+ ) : (
90
+ <div className={pillClass}>{exitContent}</div>
91
+ )}
92
+ </>
93
+ )}
94
+ </div>
95
+
96
+ {interact && (
97
+ <div className="absolute top-1/2 left-1/2 mt-7 -translate-x-1/2 whitespace-nowrap">
98
+ <div className="corner-smooth flex items-center gap-1.5 rounded-full border border-border/40 bg-background/80 px-3 py-1 text-xs shadow-elevation-3 backdrop-blur-xl">
99
+ <kbd className="rounded border border-border/60 bg-white/10 px-1.5 py-0.5 font-mono text-[10px]">
100
+ E
101
+ </kbd>
102
+ <span className="text-muted-foreground">or click to</span>
103
+ <span className="font-medium">
104
+ {interact.verb} {interact.label}
105
+ </span>
106
+ </div>
107
+ </div>
108
+ )}
109
+ </div>
110
+ )
111
+ }
@@ -0,0 +1,14 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { isSuspiciousNodeDrop } from './use-auto-save'
3
+
4
+ describe('isSuspiciousNodeDrop', () => {
5
+ test('blocks populated scenes from being flushed as empty skeletons', () => {
6
+ expect(isSuspiciousNodeDrop(12, 0)).toBe(true)
7
+ expect(isSuspiciousNodeDrop(12, 4)).toBe(true)
8
+ })
9
+
10
+ test('allows ordinary edits and intentionally empty starting scenes', () => {
11
+ expect(isSuspiciousNodeDrop(12, 11)).toBe(false)
12
+ expect(isSuspiciousNodeDrop(4, 0)).toBe(false)
13
+ })
14
+ })
@@ -5,6 +5,11 @@ import { type MutableRefObject, useCallback, useEffect, useRef } from 'react'
5
5
  import { type SceneGraph, saveSceneToLocalStorage } from '../lib/scene'
6
6
 
7
7
  const AUTOSAVE_DEBOUNCE_MS = 1000
8
+ const STRUCTURAL_NODE_COUNT = 4
9
+
10
+ export function isSuspiciousNodeDrop(previousNodeCount: number, currentNodeCount: number) {
11
+ return previousNodeCount > STRUCTURAL_NODE_COUNT && currentNodeCount <= STRUCTURAL_NODE_COUNT
12
+ }
8
13
 
9
14
  export type SaveStatus = 'idle' | 'pending' | 'saving' | 'saved' | 'paused' | 'error'
10
15
 
@@ -61,6 +66,13 @@ export function useAutoSave({
61
66
  useEffect(() => {
62
67
  let lastNodesSnapshot = JSON.stringify(useScene.getState().nodes)
63
68
  let lastNodeCount = Object.keys(useScene.getState().nodes).length
69
+ // Collections + scene materials are document-level state that persists with
70
+ // the graph but lives outside `nodes`. Track them by reference (zustand
71
+ // hands out a new object on every mutation) so a material edit or a
72
+ // collection change still triggers a save.
73
+ let lastCollectionsRef = useScene.getState().collections
74
+ let lastMaterialsRef = useScene.getState().materials
75
+ let lastInstalledPluginsRef = useScene.getState().installedPlugins
64
76
 
65
77
  async function executeSave() {
66
78
  if (isLoadingSceneRef.current || isVersionPreviewModeRef.current) {
@@ -69,14 +81,19 @@ export function useAutoSave({
69
81
  return
70
82
  }
71
83
 
72
- const { nodes, rootNodeIds } = useScene.getState()
73
- const sceneGraph = { nodes, rootNodeIds } as SceneGraph
84
+ const { nodes, rootNodeIds, collections, materials, installedPlugins } = useScene.getState()
85
+ const sceneGraph = {
86
+ nodes,
87
+ rootNodeIds,
88
+ collections,
89
+ materials,
90
+ installedPlugins,
91
+ } as SceneGraph
74
92
 
75
93
  // Guard: refuse to autosave if the scene went from populated to nearly empty.
76
94
  // This catches accidental full deletions before they're persisted.
77
95
  const currentNodeCount = Object.keys(nodes).length
78
- const STRUCTURAL_NODE_COUNT = 4 // site + building + levels (empty scene skeleton)
79
- if (lastNodeCount > STRUCTURAL_NODE_COUNT && currentNodeCount <= STRUCTURAL_NODE_COUNT) {
96
+ if (isSuspiciousNodeDrop(lastNodeCount, currentNodeCount)) {
80
97
  console.warn(
81
98
  `[autosave] Blocked: scene dropped from ${lastNodeCount} to ${currentNodeCount} nodes. Likely accidental deletion.`,
82
99
  )
@@ -118,19 +135,33 @@ export function useAutoSave({
118
135
  const unsubscribe = useScene.subscribe((state) => {
119
136
  if (isLoadingSceneRef.current) {
120
137
  lastNodesSnapshot = JSON.stringify(state.nodes)
138
+ lastCollectionsRef = state.collections
139
+ lastMaterialsRef = state.materials
140
+ lastInstalledPluginsRef = state.installedPlugins
121
141
  return
122
142
  }
123
143
 
124
144
  if (isVersionPreviewModeRef.current) {
125
145
  setSaveStatus('paused')
126
146
  lastNodesSnapshot = JSON.stringify(state.nodes)
147
+ lastCollectionsRef = state.collections
148
+ lastMaterialsRef = state.materials
149
+ lastInstalledPluginsRef = state.installedPlugins
127
150
  return
128
151
  }
129
152
 
130
153
  const currentNodesSnapshot = JSON.stringify(state.nodes)
131
- if (currentNodesSnapshot === lastNodesSnapshot) return
154
+ const changed =
155
+ currentNodesSnapshot !== lastNodesSnapshot ||
156
+ state.collections !== lastCollectionsRef ||
157
+ state.materials !== lastMaterialsRef ||
158
+ state.installedPlugins !== lastInstalledPluginsRef
159
+ if (!changed) return
132
160
 
133
161
  lastNodesSnapshot = currentNodesSnapshot
162
+ lastCollectionsRef = state.collections
163
+ lastMaterialsRef = state.materials
164
+ lastInstalledPluginsRef = state.installedPlugins
134
165
  hasDirtyChangesRef.current = true
135
166
  onDirtyRef.current?.()
136
167
  setSaveStatus('pending')
@@ -155,9 +186,25 @@ export function useAutoSave({
155
186
  // (mobile Safari, bfcache) where `beforeunload` does not.
156
187
  function flushOnExit() {
157
188
  if (!hasDirtyChangesRef.current) return
189
+ const { nodes, rootNodeIds, collections, materials, installedPlugins } = useScene.getState()
190
+ const currentNodeCount = Object.keys(nodes).length
191
+ if (isSuspiciousNodeDrop(lastNodeCount, currentNodeCount)) {
192
+ console.warn(
193
+ `[autosave] Blocked unload flush: scene dropped from ${lastNodeCount} to ${currentNodeCount} nodes. Likely accidental deletion.`,
194
+ )
195
+ setSaveStatus('error')
196
+ return
197
+ }
198
+
158
199
  hasDirtyChangesRef.current = false
159
- const { nodes, rootNodeIds } = useScene.getState()
160
- const sceneGraph = { nodes, rootNodeIds } as SceneGraph
200
+ lastNodeCount = currentNodeCount
201
+ const sceneGraph = {
202
+ nodes,
203
+ rootNodeIds,
204
+ collections,
205
+ materials,
206
+ installedPlugins,
207
+ } as SceneGraph
161
208
  if (onSaveRef.current) {
162
209
  onSaveRef.current(sceneGraph, { keepalive: true }).catch(() => {})
163
210
  } else {
@@ -12,6 +12,7 @@ import { useThree } from '@react-three/fiber'
12
12
  import { useEffect, useRef } from 'react'
13
13
  import { type Object3D, Plane, Raycaster, Vector2, Vector3 } from 'three'
14
14
  import useEditor from '../store/use-editor'
15
+ import { getMovingNode } from '../store/use-interaction-scope'
15
16
 
16
17
  const UP = new Vector3(0, 1, 0)
17
18
 
@@ -59,7 +60,7 @@ export function useCeilingEvents() {
59
60
  const isActive = (): boolean => {
60
61
  const ed = useEditor.getState()
61
62
  if (ed.selectedItem?.attachTo === 'ceiling') return true
62
- const moving = ed.movingNode
63
+ const moving = getMovingNode()
63
64
  return moving?.type === 'item' && moving.asset?.attachTo === 'ceiling'
64
65
  }
65
66
 
@@ -85,7 +86,7 @@ export function useCeilingEvents() {
85
86
 
86
87
  for (const id of ceilingIds) {
87
88
  const node = nodes[id as AnyNodeId] as CeilingNode | undefined
88
- if (!node || node.type !== 'ceiling' || node.polygon.length < 3) continue
89
+ if (node?.type !== 'ceiling' || node.polygon.length < 3) continue
89
90
  if (resolveLevelId(node, nodes) !== activeLevelId) continue
90
91
  const mesh = sceneRegistry.nodes.get(id)
91
92
  if (!mesh) continue
@@ -19,7 +19,10 @@ import { useEffect, useRef } from 'react'
19
19
  const sceneApi = createSceneApi(useScene)
20
20
 
21
21
  function modifiersFromGridEvent(event: GridEvent): Modifiers {
22
- const ne = event.nativeEvent?.nativeEvent as Partial<KeyboardEvent> | undefined
22
+ // Both grid-event emit paths (use-grid-events.ts and the floorplan panel)
23
+ // store the raw DOM event directly at `.nativeEvent` — there is no second
24
+ // `.nativeEvent` hop.
25
+ const ne = event.nativeEvent as Partial<MouseEvent> | undefined
23
26
  return {
24
27
  shift: ne?.shiftKey ?? false,
25
28
  alt: ne?.altKey ?? false,
@@ -9,6 +9,8 @@ import { useViewer } from '@pascal-app/viewer'
9
9
  import { useThree } from '@react-three/fiber'
10
10
  import { useEffect, useRef } from 'react'
11
11
  import { Plane, Raycaster, Vector2, Vector3 } from 'three'
12
+ import { getPlacementSurface } from '../lib/active-placement-surface'
13
+ import { resolveTerrainGroundHit } from '../lib/ground-surface'
12
14
 
13
15
  /**
14
16
  * Custom grid events hook that uses manual raycasting instead of mesh events.
@@ -38,6 +40,28 @@ export function useGridEvents(gridY: number) {
38
40
  // Update raycaster
39
41
  raycaster.current.setFromCamera(pointer.current, camera)
40
42
 
43
+ // Sculpted ground wins over the plane, but only while the plane IS the
44
+ // ground (see `isSiteGroundPlane`): a plane riding a storey base or a slab
45
+ // top is a real flat surface and must stay planar. The march is what removes
46
+ // the perspective skew — a ray intersected at the datum instead of at the
47
+ // hillside reports an XZ that slides along the ray, so every tool consuming
48
+ // `position`/`localPosition` was placing short of the cursor on a slope.
49
+ //
50
+ // The plane's own height is the query height, not the `gridY` argument: this
51
+ // effect is keyed on `[camera, gl]` (re-attaching listeners mid-drag would
52
+ // drop the gesture), so the argument in this closure is the value from mount.
53
+ // `constant = -gridY` by construction in the effect above.
54
+ const { origin, direction } = raycaster.current.ray
55
+ const fixedConstructionPlane = getPlacementSurface()?.projection === 'fixed-plane'
56
+ const hit = fixedConstructionPlane
57
+ ? null
58
+ : resolveTerrainGroundHit(
59
+ [origin.x, origin.y, origin.z],
60
+ [direction.x, direction.y, direction.z],
61
+ -groundPlane.current.constant,
62
+ )
63
+ if (hit) return intersectionPoint.current.set(hit.x, hit.y, hit.z).clone()
64
+
41
65
  // Intersect with ground plane
42
66
  if (raycaster.current.ray.intersectPlane(groundPlane.current, intersectionPoint.current)) {
43
67
  return intersectionPoint.current.clone()