@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
@@ -0,0 +1,234 @@
1
+ import {
2
+ type AnyNode,
3
+ area,
4
+ getScaledDimensions,
5
+ getWallCurveLength,
6
+ getWallEffectiveHeightForNodes,
7
+ getWallThickness,
8
+ type ItemNode,
9
+ resolveLevelId,
10
+ type SlabNode,
11
+ type StairNode,
12
+ type WallNode,
13
+ type WindowNode,
14
+ type ZoneNode,
15
+ } from '@pascal-app/core'
16
+ import {
17
+ formatLinearMeasurement,
18
+ getAreaUnitLabel,
19
+ type LinearUnit,
20
+ type MetricNotation,
21
+ squareMetersToAreaUnit,
22
+ } from '../measurements'
23
+ import type { PointKind, PointTarget } from './types'
24
+
25
+ // Point and ask names what the finger is on (the owner, 8 October): the label chip and the
26
+ // bubble's context row read it in the viewer's unit; the agent reads the same element's `sizes` in
27
+ // metres. Pure: the scene is handed in.
28
+
29
+ export type PointUnit = { unit: LinearUnit; metricNotation?: MetricNotation }
30
+
31
+ /** A described element: the chip's fields, plus what the agent's context carries. */
32
+ export type DescribedTarget = PointTarget & {
33
+ levelId: string
34
+ /** The room it is in or bounds. */
35
+ zoneId?: string
36
+ /** The wall an opening is in, the item an item stands on. */
37
+ parentId?: string
38
+ /** Metres: wall {length, height, thickness}; zone {area, width, depth}; window/door {width,
39
+ * height, sill}; item {width, depth, height}. */
40
+ sizes: Record<string, number>
41
+ }
42
+
43
+ type Nodes = Readonly<Record<string, AnyNode | undefined>>
44
+
45
+ const KINDS: Partial<Record<string, PointKind>> = {
46
+ zone: 'room',
47
+ wall: 'wall',
48
+ window: 'window',
49
+ door: 'door',
50
+ item: 'item',
51
+ roof: 'roof',
52
+ 'roof-segment': 'roof',
53
+ stair: 'stair',
54
+ 'stair-segment': 'stair',
55
+ }
56
+
57
+ const DEFAULT_NAMES: Partial<Record<string, string>> = {
58
+ slab: 'Floor',
59
+ ceiling: 'Ceiling',
60
+ }
61
+
62
+ const sentence = (type: string) => {
63
+ const words = type.replace(/-/g, ' ')
64
+ return words.charAt(0).toUpperCase() + words.slice(1)
65
+ }
66
+
67
+ const metres = (value: number, unit: PointUnit) => {
68
+ if (unit.unit === 'imperial') return formatLinearMeasurement(value, 'imperial')
69
+ return unit.metricNotation === 'millimeters' ? `${Math.round(value * 1000)}` : value.toFixed(2)
70
+ }
71
+
72
+ /** "4.80 × 2.70 m", "3'11" × 4'7"", "1200 × 1400 mm". */
73
+ function dimensions(values: number[], unit: PointUnit): string {
74
+ const parts = values.map((value) => metres(value, unit)).join(' × ')
75
+ if (unit.unit === 'imperial') return parts
76
+ return `${parts} ${unit.metricNotation === 'millimeters' ? 'mm' : 'm'}`
77
+ }
78
+
79
+ /** "2.10 m wide". */
80
+ function wide(value: number, unit: PointUnit): string {
81
+ return `${dimensions([value], unit)} wide`
82
+ }
83
+
84
+ function areaLabel(squareMetres: number, unit: PointUnit): string {
85
+ return `${squareMetersToAreaUnit(squareMetres, unit.unit).toFixed(1)} ${getAreaUnitLabel(unit.unit)}`
86
+ }
87
+
88
+ function extent(points: readonly (readonly number[])[], axis: 0 | 1): number {
89
+ const values = points.map((point) => point[axis]!)
90
+ return values.length ? Math.max(...values) - Math.min(...values) : 0
91
+ }
92
+
93
+ function insidePolygon(point: readonly [number, number], polygon: readonly (readonly number[])[]) {
94
+ let inside = false
95
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
96
+ const [xi, zi] = [polygon[i]![0]!, polygon[i]![1]!]
97
+ const [xj, zj] = [polygon[j]![0]!, polygon[j]![1]!]
98
+ if (
99
+ zi > point[1] !== zj > point[1] &&
100
+ point[0] < ((xj - xi) * (point[1] - zi)) / (zj - zi) + xi
101
+ )
102
+ inside = !inside
103
+ }
104
+ return inside
105
+ }
106
+
107
+ const zonesOf = (nodes: Nodes, levelId: string) =>
108
+ Object.values(nodes).filter(
109
+ (node): node is ZoneNode => node?.type === 'zone' && node.parentId === levelId,
110
+ )
111
+
112
+ /** The room a wall bounds: the zone that lists it among its boundary walls. */
113
+ const roomOfWall = (nodes: Nodes, levelId: string, wallId: string) =>
114
+ zonesOf(nodes, levelId).find((zone) => zone.boundaryWallIds?.includes(wallId as never))
115
+
116
+ function wallHeight(wall: WallNode, nodes: Nodes): number {
117
+ try {
118
+ const height = getWallEffectiveHeightForNodes(wall, nodes as Record<string, AnyNode>)
119
+ if (Number.isFinite(height) && height > 0) return height
120
+ } catch {
121
+ // No slab election to consult in a bare scene: the wall's own height stands.
122
+ }
123
+ return wall.height ?? 2.5
124
+ }
125
+
126
+ export function describeTarget(node: AnyNode, nodes: Nodes, unit: PointUnit): DescribedTarget {
127
+ const levelId = resolveLevelId(node, nodes as Record<string, AnyNode>)
128
+ const named = (fallback: string) => (node as { name?: string }).name?.trim() || fallback
129
+ const base = {
130
+ id: node.id,
131
+ type: node.type,
132
+ kind: KINDS[node.type] ?? ('other' as PointKind),
133
+ levelId,
134
+ }
135
+
136
+ if (node.type === 'zone') {
137
+ const zone = node as ZoneNode
138
+ const squareMetres = area([{ outer: zone.polygon, holes: zone.holes }])
139
+ return {
140
+ ...base,
141
+ name: named('Room'),
142
+ size: areaLabel(squareMetres, unit),
143
+ sizes: {
144
+ area: squareMetres,
145
+ width: extent(zone.polygon, 0),
146
+ depth: extent(zone.polygon, 1),
147
+ },
148
+ }
149
+ }
150
+
151
+ if (node.type === 'wall') {
152
+ const wall = node as WallNode
153
+ const room = roomOfWall(nodes, levelId, wall.id)
154
+ const length = getWallCurveLength(wall)
155
+ const height = wallHeight(wall, nodes)
156
+ return {
157
+ ...base,
158
+ name: named(room ? `Wall · ${room.name?.trim() || 'Room'}` : 'Wall'),
159
+ size: dimensions([length, height], unit),
160
+ sizes: { length, height, thickness: getWallThickness(wall) },
161
+ ...(room ? { zoneId: room.id } : {}),
162
+ }
163
+ }
164
+
165
+ if (node.type === 'window' || node.type === 'door') {
166
+ const opening = node as WindowNode & { wallId?: string }
167
+ const wallId = opening.wallId ?? opening.parentId ?? undefined
168
+ const room = wallId ? roomOfWall(nodes, levelId, wallId) : undefined
169
+ return {
170
+ ...base,
171
+ name: named(node.type === 'window' ? 'Window' : 'Door'),
172
+ size: dimensions([opening.width, opening.height], unit),
173
+ sizes: {
174
+ width: opening.width,
175
+ height: opening.height,
176
+ sill: Math.max(0, opening.position[1] - opening.height / 2),
177
+ },
178
+ ...(opening.parentId ? { parentId: opening.parentId } : {}),
179
+ ...(room ? { zoneId: room.id } : {}),
180
+ }
181
+ }
182
+
183
+ if (node.type === 'item') {
184
+ const item = node as ItemNode
185
+ const [width, height, depth] = getScaledDimensions(item)
186
+ const room = zonesOf(nodes, levelId).find((zone) =>
187
+ insidePolygon([item.position[0], item.position[2]], zone.polygon),
188
+ )
189
+ const onItem = item.parentId && nodes[item.parentId]?.type === 'item'
190
+ return {
191
+ ...base,
192
+ name: named(item.asset.name?.trim() || 'Item'),
193
+ size: wide(width, unit),
194
+ sizes: { width, depth, height },
195
+ ...(onItem ? { parentId: item.parentId as string } : {}),
196
+ ...(room ? { zoneId: room.id } : {}),
197
+ }
198
+ }
199
+
200
+ if (node.type === 'roof-segment') {
201
+ const { width, depth } = node as { width: number; depth: number }
202
+ return {
203
+ ...base,
204
+ name: named('Roof segment'),
205
+ size: dimensions([width, depth], unit),
206
+ sizes: { width, depth },
207
+ }
208
+ }
209
+
210
+ if (node.type === 'stair' || node.type === 'stair-segment') {
211
+ const stair = node as StairNode
212
+ const sizes: Record<string, number> = { width: stair.width ?? 1 }
213
+ if (stair.totalRise !== undefined) sizes.rise = stair.totalRise
214
+ return { ...base, name: named('Stair'), size: wide(sizes.width!, unit), sizes }
215
+ }
216
+
217
+ if (node.type === 'slab' || node.type === 'ceiling') {
218
+ const plate = node as SlabNode
219
+ const squareMetres = area([{ outer: plate.polygon, holes: [] }])
220
+ return {
221
+ ...base,
222
+ name: named(DEFAULT_NAMES[node.type] ?? sentence(node.type)),
223
+ size: areaLabel(squareMetres, unit),
224
+ sizes: { area: squareMetres },
225
+ }
226
+ }
227
+
228
+ return {
229
+ ...base,
230
+ name: named(DEFAULT_NAMES[node.type] ?? sentence(node.type)),
231
+ size: '',
232
+ sizes: {},
233
+ }
234
+ }
@@ -0,0 +1,79 @@
1
+ // Dictate in the bubble (the owner said yes to voice, 8 October): the browser's own speech
2
+ // recognition where it exists, hidden where it does not. The words fill the field and never send:
3
+ // the person reads them and presses Enter.
4
+
5
+ type RecognitionResult = { 0: { transcript: string }; isFinal: boolean; length: number }
6
+ type RecognitionEvent = { resultIndex: number; results: ArrayLike<RecognitionResult> }
7
+ type Recognition = {
8
+ lang: string
9
+ interimResults: boolean
10
+ continuous: boolean
11
+ onresult: ((event: RecognitionEvent) => void) | null
12
+ onend: (() => void) | null
13
+ onerror: ((event: { error: string }) => void) | null
14
+ start: () => void
15
+ stop: () => void
16
+ abort: () => void
17
+ }
18
+ type RecognitionConstructor = new () => Recognition
19
+
20
+ function constructorOf(): RecognitionConstructor | null {
21
+ if (typeof window === 'undefined') return null
22
+ const w = window as unknown as {
23
+ SpeechRecognition?: RecognitionConstructor
24
+ webkitSpeechRecognition?: RecognitionConstructor
25
+ }
26
+ return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null
27
+ }
28
+
29
+ export function isDictationSupported(): boolean {
30
+ return constructorOf() !== null
31
+ }
32
+
33
+ export type Dictation = { stop: () => void }
34
+
35
+ /**
36
+ * Starts listening. `onText` gets the whole transcript so far (interim words included) each time
37
+ * it changes; `onEnd` fires once when listening stops, by `stop`, by silence or by an error.
38
+ */
39
+ export function startDictation(options: {
40
+ lang?: string
41
+ onText: (text: string) => void
42
+ onEnd: (reason: 'stopped' | 'error', error?: string) => void
43
+ }): Dictation | null {
44
+ const Ctor = constructorOf()
45
+ if (!Ctor) return null
46
+ const recognition = new Ctor()
47
+ recognition.lang =
48
+ options.lang ?? (typeof navigator === 'undefined' ? 'en-US' : navigator.language)
49
+ recognition.interimResults = true
50
+ recognition.continuous = true
51
+ let finished = false
52
+ const finish = (reason: 'stopped' | 'error', error?: string) => {
53
+ if (finished) return
54
+ finished = true
55
+ options.onEnd(reason, error)
56
+ }
57
+ recognition.onresult = (event) => {
58
+ let text = ''
59
+ for (let i = 0; i < event.results.length; i++) text += event.results[i]?.[0]?.transcript ?? ''
60
+ options.onText(text.trim())
61
+ }
62
+ recognition.onerror = (event) => finish('error', event.error)
63
+ recognition.onend = () => finish('stopped')
64
+ try {
65
+ recognition.start()
66
+ } catch (error) {
67
+ finish('error', error instanceof Error ? error.message : String(error))
68
+ return null
69
+ }
70
+ return {
71
+ stop: () => {
72
+ try {
73
+ recognition.stop()
74
+ } catch {
75
+ finish('stopped')
76
+ }
77
+ },
78
+ }
79
+ }
@@ -0,0 +1,61 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { edgeMarker } from './edge-marker'
3
+
4
+ // A pin whose element is out of view (spec 2.6, "work outside the view shows an edge marker"):
5
+ // the marker sits on the viewport's edge, on the line from its centre towards the pin, and points
6
+ // at it. Written first: a marker for a pin that is in view; one off the edge on the wrong side;
7
+ // a pin behind the camera sending the marker nowhere.
8
+ const VIEW = { x0: 0, y0: 0, x1: 1000, y1: 600 }
9
+
10
+ describe('edgeMarker', () => {
11
+ test('a pin in view needs none', () => {
12
+ expect(edgeMarker({ x: 400, y: 300 }, VIEW)).toBeNull()
13
+ })
14
+
15
+ test('a pin to the right sits on the right edge, level with the line to it', () => {
16
+ const marker = edgeMarker({ x: 2000, y: 300 }, VIEW)!
17
+
18
+ expect(marker.x).toBe(1000 - 18)
19
+ expect(marker.y).toBeCloseTo(300, 5)
20
+ expect(marker.angle).toBeCloseTo(0, 5)
21
+ })
22
+
23
+ test('a pin above points up from the top edge', () => {
24
+ const marker = edgeMarker({ x: 500, y: -900 }, VIEW)!
25
+
26
+ expect(marker.y).toBe(18)
27
+ expect(marker.x).toBeCloseTo(500, 5)
28
+ expect(marker.angle).toBeCloseTo(-90, 5)
29
+ })
30
+
31
+ test('a pin beyond a corner sits in that corner region, on the line from the centre', () => {
32
+ const marker = edgeMarker({ x: 3000, y: 1800 }, VIEW)!
33
+
34
+ expect(marker.x).toBeLessThanOrEqual(1000 - 18)
35
+ expect(marker.y).toBeLessThanOrEqual(600 - 18)
36
+ // On the line through the centre (500, 300) and the pin: equal slope.
37
+ const slope = (marker.y - 300) / (marker.x - 500)
38
+ expect(slope).toBeCloseTo((1800 - 300) / (3000 - 500), 5)
39
+ })
40
+
41
+ test('is always inside the inset viewport', () => {
42
+ for (const p of [
43
+ { x: -500, y: 1200 },
44
+ { x: 1500, y: -300 },
45
+ { x: 2000, y: 640 },
46
+ { x: -50, y: 300 },
47
+ ]) {
48
+ const marker = edgeMarker(p, VIEW)!
49
+ expect(marker.x).toBeGreaterThanOrEqual(18)
50
+ expect(marker.x).toBeLessThanOrEqual(1000 - 18)
51
+ expect(marker.y).toBeGreaterThanOrEqual(18)
52
+ expect(marker.y).toBeLessThanOrEqual(600 - 18)
53
+ }
54
+ })
55
+
56
+ test('a pin behind the camera has no direction: the marker sits at the bottom, pointing down', () => {
57
+ const marker = edgeMarker(null, VIEW)!
58
+
59
+ expect(marker).toEqual({ x: 500, y: 600 - 18, angle: 90 })
60
+ })
61
+ })
@@ -0,0 +1,27 @@
1
+ import type { Rect, ScreenPoint } from './types'
2
+
3
+ /**
4
+ * Where the edge marker of an off-screen pin goes: on the viewport's inset edge, on the line from
5
+ * the viewport's centre towards the pin, turned to point at it. Null when the pin is in view; a
6
+ * pin behind the camera (`point` null) has no direction, so its marker sits at the bottom.
7
+ */
8
+ export function edgeMarker(
9
+ point: ScreenPoint | null,
10
+ viewport: Rect,
11
+ inset = 18,
12
+ ): { x: number; y: number; angle: number } | null {
13
+ const cx = (viewport.x0 + viewport.x1) / 2
14
+ const cy = (viewport.y0 + viewport.y1) / 2
15
+ const hw = (viewport.x1 - viewport.x0) / 2 - inset
16
+ const hh = (viewport.y1 - viewport.y0) / 2 - inset
17
+ if (!point) return { x: cx, y: cy + hh, angle: 90 }
18
+ const dx = point.x - cx
19
+ const dy = point.y - cy
20
+ if (Math.abs(dx) <= hw + inset && Math.abs(dy) <= hh + inset) return null
21
+ // Scale the direction until it touches the inset rectangle.
22
+ const scale = Math.min(
23
+ dx === 0 ? Number.POSITIVE_INFINITY : hw / Math.abs(dx),
24
+ dy === 0 ? Number.POSITIVE_INFINITY : hh / Math.abs(dy),
25
+ )
26
+ return { x: cx + dx * scale, y: cy + dy * scale, angle: (Math.atan2(dy, dx) * 180) / Math.PI }
27
+ }
@@ -0,0 +1,25 @@
1
+ // One requestAnimationFrame loop for everything that follows the camera (the outline corners, the
2
+ // bubble, the pins): it runs only while something subscribes, and each subscriber gets the frame's
3
+ // delta in seconds (capped, so a hidden tab does not fling springs on return).
4
+
5
+ type FrameFn = (dt: number, now: number) => void
6
+
7
+ const subscribers = new Set<FrameFn>()
8
+ let frame = 0
9
+ let last = 0
10
+
11
+ function tick(now: number) {
12
+ const dt = Math.min(0.05, last ? (now - last) / 1000 : 0.016)
13
+ last = now
14
+ for (const fn of [...subscribers]) fn(dt, now)
15
+ frame = subscribers.size > 0 ? requestAnimationFrame(tick) : 0
16
+ if (!frame) last = 0
17
+ }
18
+
19
+ export function subscribeFrame(fn: FrameFn): () => void {
20
+ subscribers.add(fn)
21
+ if (!frame && typeof requestAnimationFrame === 'function') frame = requestAnimationFrame(tick)
22
+ return () => {
23
+ subscribers.delete(fn)
24
+ }
25
+ }
@@ -0,0 +1,94 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { createdSince, markHistory, stateAfterMark, stepsSince } from './history-marks'
3
+
4
+ // Undo from a pin steps back through what the ask's turn wrote. The scene history keeps only the
5
+ // last 50 steps and a Cmd+Z shrinks it, so a length alone lies: the mark holds the last step that
6
+ // stood when the turn began, and the steps since are counted from where that step is now.
7
+ const steps = (n: number, from = 0) => Array.from({ length: n }, (_, i) => ({ id: from + i }))
8
+
9
+ describe('stepsSince', () => {
10
+ test('counts the steps pushed after the mark', () => {
11
+ const past = steps(3)
12
+ const mark = markHistory(past)
13
+ const later = [...past, ...steps(2, 3)]
14
+
15
+ expect(stepsSince(later, mark)).toBe(2)
16
+ expect(stepsSince(past, mark)).toBe(0)
17
+ })
18
+
19
+ test('an empty history at the mark counts everything since', () => {
20
+ const mark = markHistory([])
21
+
22
+ expect(stepsSince(steps(4), mark)).toBe(4)
23
+ })
24
+
25
+ test('still counts once old steps fall off the front (the history limit)', () => {
26
+ const past = steps(50)
27
+ const mark = markHistory(past)
28
+ const later = [...past, ...steps(3, 50)].slice(3)
29
+
30
+ expect(stepsSince(later, mark)).toBe(3)
31
+ })
32
+
33
+ test('is null when the marked step itself fell off, so the whole turn cannot be undone', () => {
34
+ const past = steps(50)
35
+ const mark = markHistory(past)
36
+ const later = [...past, ...steps(60, 50)].slice(-50)
37
+
38
+ expect(stepsSince(later, mark)).toBeNull()
39
+ })
40
+
41
+ test('is null after the person undid back past the mark', () => {
42
+ const past = steps(5)
43
+ const mark = markHistory(past)
44
+
45
+ expect(stepsSince(past.slice(0, 2), mark)).toBeNull()
46
+ })
47
+
48
+ test('a Cmd+Z that only took part of the turn leaves the rest countable', () => {
49
+ const past = steps(3)
50
+ const mark = markHistory(past)
51
+ const written = [...past, ...steps(4, 3)]
52
+ const afterOneUndo = written.slice(0, -1)
53
+
54
+ expect(stepsSince(afterOneUndo, mark)).toBe(3)
55
+ })
56
+ })
57
+
58
+ describe('stateAfterMark', () => {
59
+ test('is the state the first step since the mark was taken from', () => {
60
+ const past = steps(3)
61
+ const mark = markHistory(past)
62
+ const later = [...past, ...steps(2, 3)]
63
+
64
+ expect(stateAfterMark(later, mark)).toBe(later[3] ?? null)
65
+ })
66
+
67
+ test('with an empty history at the mark it is the oldest state', () => {
68
+ const mark = markHistory([])
69
+ const later = steps(2)
70
+
71
+ expect(stateAfterMark(later, mark)).toBe(later[0] ?? null)
72
+ })
73
+
74
+ test('is null when nothing was pushed since the mark, or the marked step is gone', () => {
75
+ const past = steps(3)
76
+ const mark = markHistory(past)
77
+
78
+ expect(stateAfterMark(past, mark)).toBeNull()
79
+ expect(stateAfterMark(steps(2, 10), mark)).toBeNull()
80
+ })
81
+ })
82
+
83
+ describe('createdSince', () => {
84
+ test('is the nodes the scene has now that the earlier state did not', () => {
85
+ const before = { wall_a: {}, window_a: {} }
86
+ const now = { wall_a: {}, window_a: {}, window_b: {}, wall_b: {} }
87
+
88
+ expect(createdSince(before, now).sort()).toEqual(['wall_b', 'window_b'])
89
+ })
90
+
91
+ test('a node only changed is not created, and one deleted is not either', () => {
92
+ expect(createdSince({ a: {}, b: {} }, { a: { width: 2 } })).toEqual([])
93
+ })
94
+ })
@@ -0,0 +1,39 @@
1
+ // Counting what an ask's turn wrote in the scene's undo history. The history is bounded (the last
2
+ // 50 steps) and the person can Cmd+Z into it, so a length cannot say how many steps are the turn's:
3
+ // a mark holds the last step standing when the turn began, and the steps since are counted from
4
+ // where that step is now.
5
+
6
+ export type HistoryMark = { ref: unknown | null; length: number }
7
+
8
+ export function markHistory(past: readonly unknown[]): HistoryMark {
9
+ return { ref: past.at(-1) ?? null, length: past.length }
10
+ }
11
+
12
+ /**
13
+ * How many steps were pushed since `mark`, or null when the marked step is gone from the history
14
+ * (it fell off the front, or the person undid back past it): the turn can no longer be undone as a
15
+ * whole.
16
+ */
17
+ export function stepsSince(past: readonly unknown[], mark: HistoryMark): number | null {
18
+ if (mark.ref === null) return past.length
19
+ const at = past.lastIndexOf(mark.ref)
20
+ return at < 0 ? null : past.length - 1 - at
21
+ }
22
+
23
+ /**
24
+ * The history state the turn's first step was taken from: the scene as it stood when the turn began.
25
+ * Null when nothing was pushed since the mark, or the marked step has gone from the history.
26
+ */
27
+ export function stateAfterMark<T>(past: readonly T[], mark: HistoryMark): T | null {
28
+ if (mark.ref === null) return past[0] ?? null
29
+ const at = past.lastIndexOf(mark.ref as T)
30
+ return at < 0 ? null : (past[at + 1] ?? null)
31
+ }
32
+
33
+ /** The nodes in `now` that `before` had no entry for: what an undo of the steps in between will remove. */
34
+ export function createdSince(
35
+ before: Record<string, unknown>,
36
+ now: Record<string, unknown>,
37
+ ): string[] {
38
+ return Object.keys(now).filter((id) => !(id in before))
39
+ }
@@ -0,0 +1,32 @@
1
+ import { usePointAsk } from '../../store/use-point-ask'
2
+ import { chatWentIdle, retryAsk } from './session'
3
+
4
+ // What a host app (the hosted chat) tells Point and ask about itself, without importing the store.
5
+
6
+ /** The host's "Playful touches" setting: false turns the region flash, the ripples and the sounds off. */
7
+ export function setPointAskPlayful(playful: boolean) {
8
+ usePointAsk.getState().setPlayful(playful)
9
+ }
10
+
11
+ /** Pascal is on a turn: the bubble's Send says Queue, and a new ask waits its turn. */
12
+ export function setPointAskBusy(busy: boolean) {
13
+ usePointAsk.getState().setBusy(busy)
14
+ if (!busy) chatWentIdle()
15
+ }
16
+
17
+ /**
18
+ * Messages already wait in the chat's queue (or are held there after an error): a new ask goes
19
+ * behind them, and the bubble says Queue, though Pascal is on no turn.
20
+ */
21
+ export function setPointAskBehindQueue(behindQueue: boolean) {
22
+ usePointAsk.getState().setBehindQueue(behindQueue)
23
+ }
24
+
25
+ /**
26
+ * The chat's Retry on a message that was a Point and ask: the same ask again, with its own pin. False
27
+ * when the editor no longer has the pin (a reload, a collaborator's ask): the chat sends the words
28
+ * again as a plain message.
29
+ */
30
+ export function retryPointAsk(askId: string): Promise<boolean> {
31
+ return retryAsk(askId)
32
+ }
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { outlineBox } from './outline-box'
3
+
4
+ // A room is a floor to the person pointing: its corner brackets frame the footprint, not the
5
+ // storey-high box that would leave two of them floating in the sky (seen on a two-storey house).
6
+ describe('outlineBox', () => {
7
+ const tall = {
8
+ min: [0, 0, 0] as [number, number, number],
9
+ max: [6, 4.5, 6] as [number, number, number],
10
+ }
11
+
12
+ test('a room is its footprint at the floor', () => {
13
+ expect(outlineBox('zone', tall)).toEqual({ min: [0, 0, 0], max: [6, 0.02, 6] })
14
+ })
15
+
16
+ test('everything else keeps its own box', () => {
17
+ for (const type of ['wall', 'window', 'door', 'item', 'stair', 'roof']) {
18
+ expect(outlineBox(type, tall)).toEqual(tall)
19
+ }
20
+ })
21
+
22
+ test('a room that already is flat is left as it is', () => {
23
+ const flat = {
24
+ min: [0, 2.55, 0] as [number, number, number],
25
+ max: [4, 2.55, 4] as [number, number, number],
26
+ }
27
+
28
+ expect(outlineBox('zone', flat)).toEqual({ min: [0, 2.55, 0], max: [4, 2.57, 4] })
29
+ })
30
+ })
@@ -0,0 +1,11 @@
1
+ import type { WorldBox } from '../../store/use-point-ask'
2
+
3
+ /**
4
+ * The box the overlay frames for a pointed element: its own world box, except a room, whose box
5
+ * spans the storey but which the person means as a floor. A flat footprint keeps the corner
6
+ * brackets on the ground and the bubble beside the room, not up in the air.
7
+ */
8
+ export function outlineBox(type: string, box: WorldBox): WorldBox {
9
+ if (type !== 'zone') return box
10
+ return { min: [...box.min], max: [box.max[0], box.min[1] + 0.02, box.max[2]] }
11
+ }
@@ -0,0 +1,6 @@
1
+ // Where the editor floats its own chrome over the canvas, so a bubble keeps clear of it.
2
+ export const VIEWPORT_INSET = 12
3
+ /** The top bar and the camera controls strip. */
4
+ export const BUBBLE_TOP_INSET = 56
5
+ /** The action bar at the bottom centre. */
6
+ export const BUBBLE_BOTTOM_INSET = 84