@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
@@ -43,6 +43,9 @@ type FloorplanDraftPreviewState = {
43
43
  /** First corner of an open rectangle-wall draft; the live cursor is the
44
44
  * opposite corner, so the rectangle tool publishes only per click. */
45
45
  wallRectangleDraftStart: WallPlanPoint | null
46
+ /** Corners placed so far by an open Polygon room. Draft only — no wall is
47
+ * written until the polygon completes (`lib/wall-polygon-draft.ts`). */
48
+ wallPolygonDraftPoints: WallPlanPoint[]
46
49
  roofDraftQuarterTurn: boolean
47
50
  polygonDraftType: FloorplanPolygonDraftType | null
48
51
  polygonDraftPoints: WallPlanPoint[]
@@ -59,6 +62,7 @@ type FloorplanDraftPreviewState = {
59
62
  setFenceDraftStart(point: WallPlanPoint | null): void
60
63
  setRoofDraftStart(point: WallPlanPoint | null): void
61
64
  setWallRectangleDraftStart(point: WallPlanPoint | null): void
65
+ setWallPolygonDraftPoints(points: readonly WallPlanPoint[]): void
62
66
  setRoofDraftQuarterTurn(quarterTurn: boolean): void
63
67
  setPolygonDraft(type: FloorplanPolygonDraftType | null, points: readonly WallPlanPoint[]): void
64
68
  reset(): void
@@ -102,6 +106,7 @@ export const useFloorplanDraftPreview = create<FloorplanDraftPreviewState>((set)
102
106
  fenceDraftStart: null,
103
107
  roofDraftStart: null,
104
108
  wallRectangleDraftStart: null,
109
+ wallPolygonDraftPoints: [],
105
110
  roofDraftQuarterTurn: false,
106
111
  polygonDraftType: null,
107
112
  polygonDraftPoints: [],
@@ -126,6 +131,12 @@ export const useFloorplanDraftPreview = create<FloorplanDraftPreviewState>((set)
126
131
  setFenceDraftStart: (point) => set(setPlanPointField('fenceDraftStart', point)),
127
132
  setRoofDraftStart: (point) => set(setPlanPointField('roofDraftStart', point)),
128
133
  setWallRectangleDraftStart: (point) => set(setPlanPointField('wallRectangleDraftStart', point)),
134
+ setWallPolygonDraftPoints: (points) =>
135
+ set((state) =>
136
+ planPointsEqual(state.wallPolygonDraftPoints, points)
137
+ ? state
138
+ : { wallPolygonDraftPoints: points.map(([x, z]) => [x, z]) },
139
+ ),
129
140
  setRoofDraftQuarterTurn: (quarterTurn) =>
130
141
  set((state) =>
131
142
  state.roofDraftQuarterTurn === quarterTurn ? state : { roofDraftQuarterTurn: quarterTurn },
@@ -147,6 +158,7 @@ export const useFloorplanDraftPreview = create<FloorplanDraftPreviewState>((set)
147
158
  state.fenceDraftStart === null &&
148
159
  state.roofDraftStart === null &&
149
160
  state.wallRectangleDraftStart === null &&
161
+ state.wallPolygonDraftPoints.length === 0 &&
150
162
  state.roofDraftQuarterTurn === false &&
151
163
  state.polygonDraftType === null &&
152
164
  state.polygonDraftPoints.length === 0
@@ -161,6 +173,7 @@ export const useFloorplanDraftPreview = create<FloorplanDraftPreviewState>((set)
161
173
  fenceDraftStart: null,
162
174
  roofDraftStart: null,
163
175
  wallRectangleDraftStart: null,
176
+ wallPolygonDraftPoints: [],
164
177
  roofDraftQuarterTurn: false,
165
178
  polygonDraftType: null,
166
179
  polygonDraftPoints: [],
@@ -13,6 +13,7 @@
13
13
 
14
14
  import type { WallPlanPoint } from '@pascal-app/core'
15
15
  import { create } from 'zustand'
16
+ import useInteractionScope from './use-interaction-scope'
16
17
 
17
18
  export type FloorplanMarqueeDrag = {
18
19
  pointerId: number
@@ -34,7 +35,10 @@ type FloorplanMarqueeState = {
34
35
 
35
36
  export const useFloorplanMarquee = create<FloorplanMarqueeState>((set) => ({
36
37
  drag: null,
37
- begin: (drag) => set({ drag }),
38
+ begin: (drag) => {
39
+ useInteractionScope.getState().begin({ kind: 'box-select' })
40
+ set({ drag })
41
+ },
38
42
  setCurrent: (point) =>
39
43
  set((state) => {
40
44
  const prev = state.drag
@@ -44,7 +48,13 @@ export const useFloorplanMarquee = create<FloorplanMarqueeState>((set) => ({
44
48
  }
45
49
  return { drag: { ...prev, currentPlanPoint: point } }
46
50
  }),
47
- reset: () => set((state) => (state.drag === null ? state : { drag: null })),
51
+ reset: () => {
52
+ set((state) => {
53
+ if (!state.drag) return state
54
+ useInteractionScope.getState().endIf((scope) => scope.kind === 'box-select')
55
+ return { drag: null }
56
+ })
57
+ },
48
58
  }))
49
59
 
50
60
  export default useFloorplanMarquee
@@ -0,0 +1,62 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test'
2
+ import { FOLLOW_PASCAL_STORAGE_KEY, useFollowPascal } from './use-follow-pascal'
3
+
4
+ // Follow Pascal's switch and its pause: touching the camera pauses it and offers "Resume following";
5
+ // a new build starts it again; the switch itself is the person's, kept across sessions.
6
+ beforeEach(() => {
7
+ useFollowPascal.setState({ on: true, paused: false, building: false })
8
+ })
9
+
10
+ describe('Follow Pascal', () => {
11
+ test('is on until the person turns it off, and turning it off clears a pause', () => {
12
+ expect(useFollowPascal.getState().on).toBe(true)
13
+ useFollowPascal.getState().pause()
14
+ expect(useFollowPascal.getState().paused).toBe(true)
15
+ useFollowPascal.getState().setOn(false)
16
+ expect(useFollowPascal.getState()).toMatchObject({ on: false, paused: false })
17
+ })
18
+
19
+ test('a pause waits for the person, and only matters while it is on', () => {
20
+ useFollowPascal.setState({ on: false })
21
+ useFollowPascal.getState().pause()
22
+ expect(useFollowPascal.getState().paused).toBe(false)
23
+ useFollowPascal.setState({ on: true })
24
+ useFollowPascal.getState().pause()
25
+ expect(useFollowPascal.getState().paused).toBe(true)
26
+ useFollowPascal.getState().resume()
27
+ expect(useFollowPascal.getState().paused).toBe(false)
28
+ })
29
+
30
+ test('a build that begins takes the camera again, even after a pause', () => {
31
+ useFollowPascal.getState().pause()
32
+ useFollowPascal.getState().beginBuild()
33
+ expect(useFollowPascal.getState()).toMatchObject({ paused: false, building: true })
34
+ useFollowPascal.getState().endBuild()
35
+ expect(useFollowPascal.getState().building).toBe(false)
36
+ })
37
+
38
+ test('only the switch is kept across sessions', () => {
39
+ const persisted = (
40
+ useFollowPascal as unknown as {
41
+ persist: { getOptions(): { partialize?: (state: unknown) => unknown; name?: string } }
42
+ }
43
+ ).persist.getOptions()
44
+ expect(persisted.name).toBe(FOLLOW_PASCAL_STORAGE_KEY)
45
+ expect(persisted.partialize?.({ on: false, paused: true, building: true })).toEqual({
46
+ on: false,
47
+ })
48
+ })
49
+
50
+ test('the pill offers itself only to a paused follower during a build', () => {
51
+ const shows = () => {
52
+ const { on, paused, building } = useFollowPascal.getState()
53
+ return on && paused && building
54
+ }
55
+ useFollowPascal.getState().beginBuild()
56
+ expect(shows()).toBe(false)
57
+ useFollowPascal.getState().pause()
58
+ expect(shows()).toBe(true)
59
+ useFollowPascal.getState().endBuild()
60
+ expect(shows()).toBe(false)
61
+ })
62
+ })
@@ -0,0 +1,53 @@
1
+ 'use client'
2
+
3
+ import { create } from 'zustand'
4
+ import { createJSONStorage, persist } from 'zustand/middleware'
5
+
6
+ export const FOLLOW_PASCAL_STORAGE_KEY = 'pascal-follow-pascal'
7
+
8
+ /** Reads `localStorage` on each call, so a page without it (a test, a sandbox) still has the store's persist API. */
9
+ const browserStorage = createJSONStorage(() => ({
10
+ getItem: (key: string) => globalThis.localStorage?.getItem(key) ?? null,
11
+ setItem: (key: string, value: string) => globalThis.localStorage?.setItem(key, value),
12
+ removeItem: (key: string) => globalThis.localStorage?.removeItem(key),
13
+ }))
14
+
15
+ type FollowPascalState = {
16
+ /** The person's switch: the camera follows what a build works on. Kept across sessions. */
17
+ on: boolean
18
+ /** The person took the camera: it stays theirs until they resume or the next build begins. */
19
+ paused: boolean
20
+ /** A build is playing (set by the follower from the reveal's own words). */
21
+ building: boolean
22
+ setOn: (on: boolean) => void
23
+ pause: () => void
24
+ resume: () => void
25
+ beginBuild: () => void
26
+ endBuild: () => void
27
+ }
28
+
29
+ /** Follow Pascal's switch, its pause and whether a build is being followed. */
30
+ const useFollowPascal = create<FollowPascalState>()(
31
+ persist(
32
+ (set, get) => ({
33
+ on: true,
34
+ paused: false,
35
+ building: false,
36
+ setOn: (on) => set({ on, paused: false }),
37
+ pause: () => {
38
+ if (get().on) set({ paused: true })
39
+ },
40
+ resume: () => set({ paused: false }),
41
+ beginBuild: () => set({ building: true, paused: false }),
42
+ endBuild: () => set({ building: false }),
43
+ }),
44
+ {
45
+ name: FOLLOW_PASCAL_STORAGE_KEY,
46
+ storage: browserStorage,
47
+ partialize: (state) => ({ on: state.on }),
48
+ },
49
+ ),
50
+ )
51
+
52
+ export default useFollowPascal
53
+ export { useFollowPascal }
@@ -0,0 +1,247 @@
1
+ 'use client'
2
+
3
+ import type { PointAskSubmit, PointCamera } from '@pascal-app/core'
4
+ import { create } from 'zustand'
5
+ import type { CompareRefusal } from '../lib/point-ask/compare'
6
+ import { createPin, type PinEvent, type PinModel, reducePin } from '../lib/point-ask/pin-machine'
7
+ import type { PointTarget, Rect, ScreenPoint } from '../lib/point-ask/types'
8
+
9
+ // Point and ask's state (the owner, 8 October). Everything the overlay draws comes from here, and
10
+ // everything that changes per frame (positions) is derived from it, not stored in it.
11
+
12
+ export type WorldBox = { min: [number, number, number]; max: [number, number, number] }
13
+
14
+ /** A pointed element with its world box, so the overlay can outline and anchor to it. */
15
+ export type BubbleTarget = PointTarget & { box: WorldBox | null }
16
+
17
+ export type PointHit = {
18
+ point: [number, number, number]
19
+ normal: [number, number, number]
20
+ face?: 'interior' | 'exterior' | 'top' | 'bottom'
21
+ }
22
+
23
+ export type PointHover = {
24
+ target: BubbleTarget
25
+ /** The surface the pick came through when it picked its room (the "⌥ the wall" line). */
26
+ ambiguous: boolean
27
+ hit: PointHit
28
+ }
29
+
30
+ export type PointCrop = {
31
+ state: 'none' | 'pending' | 'ready' | 'failed'
32
+ dataUrl?: string
33
+ width?: number
34
+ height?: number
35
+ marked?: boolean
36
+ }
37
+
38
+ /** A dragged rectangle (spec 2.3): drawn while the person drags, then settled until the ask goes or is dismissed. */
39
+ export type RegionState = {
40
+ rect: Rect
41
+ sizeLabel: string | null
42
+ settled: boolean
43
+ /** Changes when the shutter fires, so the marquee flashes once. */
44
+ flashKey: number
45
+ }
46
+
47
+ /** What a region bubble is about: the rectangle, the point its tail points at, and what is in it. */
48
+ export type BubbleArea = {
49
+ rect: Rect
50
+ click: ScreenPoint
51
+ ids: string[]
52
+ more: number
53
+ }
54
+
55
+ export type PointBubble = {
56
+ targets: BubbleTarget[]
57
+ /** Where the finger landed, world metres: the bubble's tail and the pin's spot. */
58
+ anchor: [number, number, number]
59
+ hit: PointHit
60
+ gesture: 'click' | 'multi' | 'region'
61
+ /** The moment of pointing (pointer-down), ISO. */
62
+ capturedAt: string
63
+ /** The view the person pointed from, recorded at pointer-down. */
64
+ camera: PointCamera | null
65
+ crop: PointCrop
66
+ draft: string
67
+ /** Why the last send did not go (out of credits, no chat): shown in the bubble, the draft kept. */
68
+ error: string | null
69
+ /** A region's rectangle, 0-1 of the canvas, when the gesture was a drag. */
70
+ region?: [number, number, number, number]
71
+ /** Set when the bubble is about a region: it is a view, not a selection, so it has no targets. */
72
+ area?: BubbleArea
73
+ /**
74
+ * Set when the bubble asks for more on what a done pin changed ("Change more"): that pin gives
75
+ * way while the bubble is open and goes when this ask is sent. The chips offer something else.
76
+ */
77
+ changesAsk?: { askId: string; after: string }
78
+ }
79
+
80
+ export type PinRecord = {
81
+ model: PinModel
82
+ /** The spot in the world at send, so the pin shows where it was planted when its target is gone. */
83
+ anchor: [number, number, number]
84
+ /** The same spot in the first target's own frame, so it rides along when Pascal moves the element. */
85
+ local: { nodeId: string; offset: [number, number, number] } | null
86
+ /** "Window · Kitchen, north wall": the chat chip's words. */
87
+ label: string
88
+ levelId: string | null
89
+ before?: PointCrop
90
+ after?: PointCrop
91
+ /** When the pin may retire (ms epoch), set once its turn ends and it is out of the person's way. */
92
+ retireAt: number | null
93
+ /** What was handed to the chat, kept for "Try again". */
94
+ submit?: PointAskSubmit
95
+ /** "Change more" was sent from this pin's card: the new ask's pin stands in for it. */
96
+ supersededBy?: string
97
+ createdAt: number
98
+ }
99
+
100
+ type PointAskState = {
101
+ active: boolean
102
+ /** A tap latched the mode; a hold keeps it only while the key is down. */
103
+ latched: boolean
104
+ hover: PointHover | null
105
+ alt: boolean
106
+ shift: boolean
107
+ bubble: PointBubble | null
108
+ /** Drafts kept per first target until the mode leaves ("no, that one" keeps the words). */
109
+ drafts: Record<string, string>
110
+ pins: Record<string, PinRecord>
111
+ pinOrder: string[]
112
+ /** The host's "Playful touches" setting: false turns the flash, ripples and sounds off. */
113
+ playful: boolean
114
+ /** Pascal is on a turn, so a new ask will wait its turn (the Send button says Queue). */
115
+ busy: boolean
116
+ /**
117
+ * Messages already wait in the chat's queue, or are held there after an error: a new ask goes
118
+ * behind them (the Send button says Queue) though no turn runs. Apart from `busy`, which says a
119
+ * turn runs and ends Follow Pascal and settles the pins.
120
+ */
121
+ behindQueue: boolean
122
+ /** The pin whose card is open (hover or focus of the pin, or its own 4 s opening). */
123
+ cardFor: string | null
124
+ /** The rectangle being dragged or just released. */
125
+ region: RegionState | null
126
+ /** The ask whose "before" is on screen while a compare control is held, or null. */
127
+ comparing: string | null
128
+ /** A hold that did not start, for its card to say why. */
129
+ compareRefusal: { askId: string; reason: CompareRefusal } | null
130
+ /** How the last bubble left, so the exit can fold into a pin (send) or just shrink (dismiss). */
131
+ closeKind: 'send' | 'dismiss'
132
+
133
+ setActive: (active: boolean, latched?: boolean) => void
134
+ setLatched: (latched: boolean) => void
135
+ setRegion: (region: RegionState | null) => void
136
+ setComparing: (askId: string | null) => void
137
+ setCompareRefusal: (refusal: { askId: string; reason: CompareRefusal } | null) => void
138
+ setModifiers: (alt: boolean, shift: boolean) => void
139
+ setHover: (hover: PointHover | null) => void
140
+ openBubble: (bubble: PointBubble) => void
141
+ patchBubble: (patch: Partial<PointBubble>) => void
142
+ closeBubble: (kind?: 'send' | 'dismiss') => void
143
+ rememberDraft: (key: string, text: string) => void
144
+ addPin: (pin: PinRecord) => void
145
+ patchPin: (askId: string, patch: Partial<Omit<PinRecord, 'model'>>) => void
146
+ dispatchPin: (askId: string, event: PinEvent) => void
147
+ removePin: (askId: string) => void
148
+ setPlayful: (playful: boolean) => void
149
+ setBusy: (busy: boolean) => void
150
+ setBehindQueue: (behindQueue: boolean) => void
151
+ openCard: (askId: string | null) => void
152
+ }
153
+
154
+ export const usePointAsk = create<PointAskState>((set) => ({
155
+ active: false,
156
+ latched: false,
157
+ hover: null,
158
+ alt: false,
159
+ shift: false,
160
+ bubble: null,
161
+ drafts: {},
162
+ pins: {},
163
+ pinOrder: [],
164
+ playful: true,
165
+ busy: false,
166
+ behindQueue: false,
167
+ cardFor: null,
168
+ closeKind: 'dismiss',
169
+ region: null,
170
+ comparing: null,
171
+ compareRefusal: null,
172
+
173
+ setActive: (active, latched = false) =>
174
+ set((state) =>
175
+ active === state.active && latched === state.latched
176
+ ? state
177
+ : active
178
+ ? { active, latched }
179
+ : { active: false, latched: false, hover: null, bubble: null, region: null, drafts: {} },
180
+ ),
181
+ setLatched: (latched) => set((state) => (state.latched === latched ? state : { latched })),
182
+ setRegion: (region) => set((state) => (state.region === region ? state : { region })),
183
+ setComparing: (comparing) =>
184
+ set((state) => (state.comparing === comparing ? state : { comparing })),
185
+ setCompareRefusal: (compareRefusal) => set({ compareRefusal }),
186
+ setModifiers: (alt, shift) =>
187
+ set((state) => (state.alt === alt && state.shift === shift ? state : { alt, shift })),
188
+ setHover: (hover) =>
189
+ set((state) => {
190
+ if (state.hover === hover) return state
191
+ if (state.hover && hover && state.hover.target.id === hover.target.id) {
192
+ const same =
193
+ state.hover.ambiguous === hover.ambiguous &&
194
+ state.hover.hit.point.every((v, i) => v === hover.hit.point[i])
195
+ if (same) return state
196
+ }
197
+ return { hover }
198
+ }),
199
+ openBubble: (bubble) => set({ bubble }),
200
+ patchBubble: (patch) =>
201
+ set((state) => (state.bubble ? { bubble: { ...state.bubble, ...patch } } : state)),
202
+ closeBubble: (kind = 'dismiss') =>
203
+ set((state) => (state.bubble ? { bubble: null, closeKind: kind } : state)),
204
+ rememberDraft: (key, text) =>
205
+ set((state) => {
206
+ if ((state.drafts[key] ?? '') === text) return state
207
+ const drafts = { ...state.drafts }
208
+ if (text) drafts[key] = text
209
+ else delete drafts[key]
210
+ return { drafts }
211
+ }),
212
+ addPin: (pin) =>
213
+ set((state) => ({
214
+ pins: { ...state.pins, [pin.model.askId]: pin },
215
+ pinOrder: [...state.pinOrder.filter((id) => id !== pin.model.askId), pin.model.askId],
216
+ })),
217
+ patchPin: (askId, patch) =>
218
+ set((state) => {
219
+ const pin = state.pins[askId]
220
+ return pin ? { pins: { ...state.pins, [askId]: { ...pin, ...patch } } } : state
221
+ }),
222
+ dispatchPin: (askId, event) =>
223
+ set((state) => {
224
+ const pin = state.pins[askId]
225
+ if (!pin) return state
226
+ const model = reducePin(pin.model, event)
227
+ return model === pin.model ? state : { pins: { ...state.pins, [askId]: { ...pin, model } } }
228
+ }),
229
+ removePin: (askId) =>
230
+ set((state) => {
231
+ if (!state.pins[askId]) return state
232
+ const pins = { ...state.pins }
233
+ delete pins[askId]
234
+ return { pins, pinOrder: state.pinOrder.filter((id) => id !== askId) }
235
+ }),
236
+ setPlayful: (playful) => set((state) => (state.playful === playful ? state : { playful })),
237
+ setBusy: (busy) => set((state) => (state.busy === busy ? state : { busy })),
238
+ setBehindQueue: (behindQueue) =>
239
+ set((state) => (state.behindQueue === behindQueue ? state : { behindQueue })),
240
+ openCard: (askId) => set((state) => (state.cardFor === askId ? state : { cardFor: askId })),
241
+ }))
242
+
243
+ /** Whether a new ask waits its turn: a turn runs, or messages already wait in the queue. */
244
+ export const asksWait = (state: Pick<PointAskState, 'busy' | 'behindQueue'>) =>
245
+ state.busy || state.behindQueue
246
+
247
+ export { createPin }
@@ -1,6 +1,6 @@
1
1
  import type { QuickMeasurementReport } from '@pascal-app/core'
2
2
  import { create } from 'zustand'
3
- import type { ViewMode } from './use-editor'
3
+ import type { VisibleScene } from './view-layout'
4
4
 
5
5
  export type QuickMeasurementHudSource = '2d' | '3d'
6
6
 
@@ -55,9 +55,9 @@ export const useQuickMeasurementHud = create<QuickMeasurementHudState>((set) =>
55
55
 
56
56
  export function selectQuickMeasurementHudEntry(
57
57
  state: QuickMeasurementHudState,
58
- viewMode: ViewMode,
58
+ scene: VisibleScene | null,
59
59
  ): QuickMeasurementHudEntry | null {
60
- const source = viewMode === 'split' ? state.activeSource : viewMode
60
+ const source = scene === 'split' ? state.activeSource : scene
61
61
  return source ? state.sources[source] : null
62
62
  }
63
63
 
@@ -17,11 +17,20 @@ type StairBuildPreviewState = {
17
17
  point: StairPreviewPoint | null
18
18
  /** Yaw (radians), cycled by R / T. */
19
19
  rotation: number
20
+ length: number | null
21
+ stepCount: number | null
22
+ rise: number | null
20
23
  /** Set the snapped point. No-ops (skips the store update, so subscribers
21
24
  * don't re-render) when the point is unchanged — `grid:move` fires far more
22
25
  * often than the snapped cell actually changes. */
23
26
  setPoint(point: StairPreviewPoint | null): void
24
- setPreview(point: StairPreviewPoint | null, rotation: number): void
27
+ setPreview(
28
+ point: StairPreviewPoint | null,
29
+ rotation: number,
30
+ rise?: number | null,
31
+ length?: number | null,
32
+ stepCount?: number | null,
33
+ ): void
25
34
  rotateBy(deltaRadians: number): void
26
35
  reset(): void
27
36
  }
@@ -29,6 +38,9 @@ type StairBuildPreviewState = {
29
38
  export const useStairBuildPreview = create<StairBuildPreviewState>((set) => ({
30
39
  point: null,
31
40
  rotation: 0,
41
+ rise: null,
42
+ length: null,
43
+ stepCount: null,
32
44
  setPoint: (point) =>
33
45
  set((state) => {
34
46
  const prev = state.point
@@ -36,18 +48,34 @@ export const useStairBuildPreview = create<StairBuildPreviewState>((set) => ({
36
48
  if (point && prev && prev[0] === point[0] && prev[1] === point[1]) return state
37
49
  return { point: point ? [point[0], point[1]] : null }
38
50
  }),
39
- setPreview: (point, rotation) =>
51
+ setPreview: (point, rotation, rise, length = null, stepCount = null) =>
40
52
  set((state) => {
41
53
  const samePoint =
42
54
  (!point && !state.point) ||
43
55
  Boolean(point && state.point && state.point[0] === point[0] && state.point[1] === point[1])
44
- return samePoint && state.rotation === rotation
56
+ return samePoint &&
57
+ state.rotation === rotation &&
58
+ (rise === undefined || rise === state.rise) &&
59
+ length === state.length &&
60
+ stepCount === state.stepCount
45
61
  ? state
46
- : { point: point ? [point[0], point[1]] : null, rotation }
62
+ : {
63
+ point: point ? [point[0], point[1]] : null,
64
+ rotation,
65
+ length,
66
+ stepCount,
67
+ rise: rise === undefined ? state.rise : rise,
68
+ }
47
69
  }),
48
70
  rotateBy: (deltaRadians) => set((state) => ({ rotation: state.rotation + deltaRadians })),
49
71
  reset: () =>
50
72
  set((state) =>
51
- state.point === null && state.rotation === 0 ? state : { point: null, rotation: 0 },
73
+ state.point === null &&
74
+ state.rotation === 0 &&
75
+ state.rise === null &&
76
+ state.length === null &&
77
+ state.stepCount === null
78
+ ? state
79
+ : { point: null, rotation: 0, rise: null, length: null, stepCount: null },
52
80
  ),
53
81
  }))
@@ -10,8 +10,10 @@ import { create } from 'zustand'
10
10
  * as translucent ghost meshes mid-drag; the 2D `wallFloorplanMoveTarget`
11
11
  * writes here so the floor-plan SVG layer can render the same hint.
12
12
  *
13
- * Writer: `packages/nodes/src/wall/floorplan-move.ts` (on each `apply`,
14
- * cleared on `commit` and by the move overlay's cleanup).
13
+ * Writers: `packages/nodes/src/wall/floorplan-move.ts` (on each `apply`,
14
+ * cleared on `commit` and by the move overlay's cleanup), and the wall push
15
+ * preview in `lib/room-handle-drag.ts` (the pieces a push would create,
16
+ * cleared when the push previews again, commits or cancels).
15
17
  * Reader: `packages/editor/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx`.
16
18
  */
17
19
  export type WallMoveGhostBridge = {