@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,498 @@
1
+ /**
2
+ * The Pascal Map location dossier, read for the lot drop-in.
3
+ *
4
+ * `fetchDossier` asks the parcel provider's `dossier` (the key lives with
5
+ * the host); the rest is pure: the parcel polygon and the FRONTAGE
6
+ * (the boundary shared with no neighbour — the street edges) projected
7
+ * into the site plan frame, the front edge picked from the frontage, the
8
+ * zoning setbacks read with their citation, and the facts the plan set
9
+ * prints (`siteFactsFromDossier`) with every geometry stripped.
10
+ *
11
+ * Every section carries `status`; `not_covered` / `not_available` mean
12
+ * "not answered" — never a negative finding. Unknown keys are ignored
13
+ * (the platform's additive-change policy).
14
+ */
15
+ import type { SiteDossier } from '@pascal-app/core'
16
+ import type { ParcelProvider } from './parcel-provider'
17
+
18
+ export type SectionStatus = 'available' | 'empty' | 'not_covered' | 'not_available'
19
+
20
+ export type DossierSection<T = Record<string, unknown>> = {
21
+ layer: string
22
+ name?: string
23
+ status: SectionStatus
24
+ summary?: string
25
+ data?: T | null
26
+ source?: { name?: string; kind?: string; vintage?: string; attribution?: string; note?: string }
27
+ reason?: string
28
+ hint?: string
29
+ }
30
+
31
+ export type Dossier = {
32
+ object: 'location'
33
+ as_of: string
34
+ point: { lat: number; lng: number; source: string }
35
+ address?: { formatted: string; precision: string }
36
+ layers: Record<string, DossierSection>
37
+ }
38
+
39
+ export type LngLat = readonly [number, number]
40
+ export type Pt = readonly [number, number]
41
+
42
+ export type ParcelData = {
43
+ parcel_key?: string
44
+ county?: { name?: string | null; fips?: string | null; local_code?: number | null }
45
+ situs_address?: { line1?: string; city?: string | null; zip?: string | null } | null
46
+ vintage?: string | null
47
+ area_m2?: number | null
48
+ frontage?: {
49
+ total_ft?: number
50
+ total_m?: number
51
+ segment_count?: number
52
+ geometry?: unknown
53
+ } | null
54
+ geometry?: { type?: string; geometry?: { type?: string; coordinates?: unknown } } | null
55
+ }
56
+
57
+ export type ZoningData = {
58
+ district?: string
59
+ district_description?: string | null
60
+ jurisdiction?: string
61
+ setbacks?: { front_ft?: number | null; side_ft?: number | null; rear_ft?: number | null } | null
62
+ max_height_ft?: number | null
63
+ max_far?: number | null
64
+ min_lot_sqft?: number | null
65
+ dimensional_note?: string | null
66
+ dimensional_source?: { url?: string; section?: string; retrieved_at?: string } | null
67
+ land_development_code_url?: string | null
68
+ }
69
+
70
+ export type CodeBasisData = {
71
+ climate_zone_iecc?: string | null
72
+ frost_depth_ft?: number | null
73
+ ground_snow_load_psf?: number | null
74
+ seismic_design_category?: string | null
75
+ wind_speed_mph?: number | null
76
+ wind_borne_debris_region?: boolean | null
77
+ rainfall_100yr_24hr_in?: number | null
78
+ climate_zone_title24?: string | null
79
+ seismic_sds?: number | null
80
+ seismic_sd1?: number | null
81
+ }
82
+
83
+ export type FloodData = {
84
+ is_in_flood_zone?: boolean | null
85
+ mapped?: boolean
86
+ firm_panel?: { panel?: string; effective_date?: string | null } | null
87
+ zone_at_point?: {
88
+ zone?: string
89
+ description?: string
90
+ is_in_flood_zone?: boolean
91
+ base_flood_elevation_ft?: number | null
92
+ bfe_datum?: string
93
+ } | null
94
+ highest_risk_zone_on_parcel?: string | null
95
+ }
96
+
97
+ export type DossierResult =
98
+ | { ok: true; dossier: Dossier }
99
+ | { ok: false; reason: string; code?: string; retryAfterS?: number }
100
+
101
+ /** Ask the provider's dossier. Never throws: a failure is a reason. */
102
+ export async function fetchDossier(
103
+ provider: ParcelProvider,
104
+ input: { address?: string; latitude?: number; longitude?: number; layers?: string[] },
105
+ ): Promise<DossierResult> {
106
+ try {
107
+ const json = (await provider('dossier', input)) as {
108
+ ok: boolean
109
+ dossier?: Dossier
110
+ reason?: string
111
+ code?: string
112
+ retryAfterS?: number
113
+ }
114
+ if (json.ok && json.dossier?.layers) return { ok: true, dossier: json.dossier }
115
+ return {
116
+ ok: false,
117
+ reason: json.reason ?? 'no dossier in the answer',
118
+ code: json.code,
119
+ retryAfterS: json.retryAfterS,
120
+ }
121
+ } catch (error) {
122
+ return { ok: false, reason: error instanceof Error ? error.message : 'dossier lookup failed' }
123
+ }
124
+ }
125
+
126
+ /** A section by layer name, or null when the dossier has none. */
127
+ export function section<T = Record<string, unknown>>(
128
+ dossier: Dossier | null | undefined,
129
+ layer: string,
130
+ ): DossierSection<T> | null {
131
+ const s = dossier?.layers?.[layer]
132
+ return s && typeof s === 'object' ? (s as DossierSection<T>) : null
133
+ }
134
+
135
+ /** A section's data when it answered (`available`), else null. */
136
+ export function answered<T = Record<string, unknown>>(
137
+ dossier: Dossier | null | undefined,
138
+ layer: string,
139
+ ): T | null {
140
+ const s = section<T>(dossier, layer)
141
+ return s && s.status === 'available' && s.data && typeof s.data === 'object' ? s.data : null
142
+ }
143
+
144
+ /* ----------------------------------------------------------- projection */
145
+
146
+ /** 1° of latitude ≈ 364,000 ft — the same flat projection apps/editor/lib/parcel/project.ts uses. */
147
+ const FEET_PER_DEG_LAT = 364000
148
+ const METRES_PER_FOOT = 0.3048
149
+ const DEG2RAD = Math.PI / 180
150
+
151
+ /** `[lng, lat]` → plan METRES with `origin` at (0, 0): x east, z south. */
152
+ export function planPointFromLngLat(origin: LngLat, p: LngLat): Pt {
153
+ const [oLng, oLat] = origin
154
+ const ftPerDegLng = FEET_PER_DEG_LAT * Math.cos(oLat * DEG2RAD)
155
+ return [
156
+ (p[0] - oLng) * ftPerDegLng * METRES_PER_FOOT,
157
+ -(p[1] - oLat) * FEET_PER_DEG_LAT * METRES_PER_FOOT,
158
+ ]
159
+ }
160
+
161
+ const isLngLat = (v: unknown): v is [number, number] =>
162
+ Array.isArray(v) &&
163
+ v.length >= 2 &&
164
+ Number.isFinite(Number(v[0])) &&
165
+ Number.isFinite(Number(v[1]))
166
+
167
+ function ringArea(ring: readonly Pt[]): number {
168
+ let a = 0
169
+ for (let i = 0; i < ring.length; i++) {
170
+ const p = ring[i] as Pt
171
+ const q = ring[(i + 1) % ring.length] as Pt
172
+ a += p[0] * q[1] - q[0] * p[1]
173
+ }
174
+ return Math.abs(a) / 2
175
+ }
176
+
177
+ /**
178
+ * The parcel's outer ring in plan metres: the largest polygon of a
179
+ * Polygon / MultiPolygon Feature, closing vertex dropped, consecutive
180
+ * duplicates removed. Empty when the section carries no usable geometry.
181
+ */
182
+ export function parcelRingMetres(parcel: ParcelData | null | undefined, origin: LngLat): Pt[] {
183
+ const geom = parcel?.geometry?.geometry
184
+ if (!geom || typeof geom !== 'object') return []
185
+ const type = geom.type
186
+ const coords = geom.coordinates as unknown
187
+ const rings: LngLat[][] = []
188
+ if (type === 'Polygon' && Array.isArray(coords) && Array.isArray(coords[0])) {
189
+ rings.push((coords[0] as unknown[]).filter(isLngLat) as LngLat[])
190
+ } else if (type === 'MultiPolygon' && Array.isArray(coords)) {
191
+ for (const poly of coords as unknown[]) {
192
+ if (Array.isArray(poly) && Array.isArray(poly[0]))
193
+ rings.push((poly[0] as unknown[]).filter(isLngLat) as LngLat[])
194
+ }
195
+ }
196
+ let best: Pt[] = []
197
+ let bestArea = 0
198
+ for (const ring of rings) {
199
+ const pts: Pt[] = []
200
+ for (const ll of ring) {
201
+ const p = planPointFromLngLat(origin, ll)
202
+ const last = pts[pts.length - 1]
203
+ if (last && Math.abs(last[0] - p[0]) < 1e-6 && Math.abs(last[1] - p[1]) < 1e-6) continue
204
+ pts.push(p)
205
+ }
206
+ const first = pts[0]
207
+ const last = pts[pts.length - 1]
208
+ if (
209
+ pts.length > 3 &&
210
+ first &&
211
+ last &&
212
+ Math.abs(first[0] - last[0]) < 1e-6 &&
213
+ Math.abs(first[1] - last[1]) < 1e-6
214
+ )
215
+ pts.pop()
216
+ if (pts.length < 3) continue
217
+ const area = ringArea(pts)
218
+ if (area > bestArea) {
219
+ bestArea = area
220
+ best = pts
221
+ }
222
+ }
223
+ return best
224
+ }
225
+
226
+ /** The frontage as plan-metre segments (every consecutive pair of a MultiLineString / LineString). */
227
+ export function frontageSegmentsMetres(
228
+ parcel: ParcelData | null | undefined,
229
+ origin: LngLat,
230
+ ): [Pt, Pt][] {
231
+ const geom = parcel?.frontage?.geometry as { type?: string; coordinates?: unknown } | undefined
232
+ if (!geom || typeof geom !== 'object') return []
233
+ const lines: LngLat[][] = []
234
+ if (geom.type === 'LineString' && Array.isArray(geom.coordinates))
235
+ lines.push((geom.coordinates as unknown[]).filter(isLngLat) as LngLat[])
236
+ else if (geom.type === 'MultiLineString' && Array.isArray(geom.coordinates)) {
237
+ for (const line of geom.coordinates as unknown[])
238
+ if (Array.isArray(line)) lines.push((line as unknown[]).filter(isLngLat) as LngLat[])
239
+ }
240
+ const out: [Pt, Pt][] = []
241
+ for (const line of lines) {
242
+ const pts = line.map((ll) => planPointFromLngLat(origin, ll))
243
+ for (let i = 0; i + 1 < pts.length; i++) out.push([pts[i] as Pt, pts[i + 1] as Pt])
244
+ }
245
+ return out
246
+ }
247
+
248
+ function pointSegmentDistance(p: Pt, a: Pt, b: Pt): number {
249
+ const abx = b[0] - a[0]
250
+ const abz = b[1] - a[1]
251
+ const l2 = abx * abx + abz * abz
252
+ const t =
253
+ l2 < 1e-12 ? 0 : Math.max(0, Math.min(1, ((p[0] - a[0]) * abx + (p[1] - a[1]) * abz) / l2))
254
+ return Math.hypot(p[0] - (a[0] + abx * t), p[1] - (a[1] + abz * t))
255
+ }
256
+
257
+ export type FrontageMatch = {
258
+ /** The lot edge index (points[index] → points[index + 1]). */
259
+ index: number
260
+ lengthM: number
261
+ /** How many lot edges front the street / water — a corner lot has two or more. */
262
+ frontingEdges: number
263
+ /** Every fronting edge index (a corner lot has two or more). */
264
+ edges: number[]
265
+ }
266
+
267
+ /**
268
+ * The front edge from the FRONTAGE: a lot edge fronts when most of its
269
+ * length lies on a frontage segment (five samples along it, each within
270
+ * `tolM` — the ring is cleaned after the fabric was cut, so the tolerance
271
+ * is generous). Of the fronting edges the LONGEST is the front (a corner
272
+ * lot's long street). Null when no edge fronts.
273
+ */
274
+ export function detectFrontEdgeFromFrontage(
275
+ ring: readonly Pt[],
276
+ segments: readonly [Pt, Pt][],
277
+ tolM = 1.5,
278
+ ): FrontageMatch | null {
279
+ if (ring.length < 3 || segments.length === 0) return null
280
+ let best: FrontageMatch | null = null
281
+ let fronting = 0
282
+ const edges: number[] = []
283
+ for (let i = 0; i < ring.length; i++) {
284
+ const a = ring[i] as Pt
285
+ const b = ring[(i + 1) % ring.length] as Pt
286
+ const len = Math.hypot(b[0] - a[0], b[1] - a[1])
287
+ if (len < 0.5) continue
288
+ let hits = 0
289
+ for (const t of [0.1, 0.3, 0.5, 0.7, 0.9]) {
290
+ const p: Pt = [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]
291
+ if (segments.some(([s0, s1]) => pointSegmentDistance(p, s0, s1) <= tolM)) hits += 1
292
+ }
293
+ if (hits < 3) continue
294
+ fronting += 1
295
+ edges.push(i)
296
+ if (!best || len > best.lengthM) best = { index: i, lengthM: len, frontingEdges: 0, edges: [] }
297
+ }
298
+ return best ? { ...best, frontingEdges: fronting, edges } : null
299
+ }
300
+
301
+ /* ----------------------------------------------------------- terrain contours */
302
+
303
+ export type ElevationData = {
304
+ terrain_status?: string
305
+ terrain?: {
306
+ min_ft?: number
307
+ max_ft?: number
308
+ contour_interval_ft?: number
309
+ contour_count?: number
310
+ datum?: string
311
+ source_resolution_m?: number
312
+ geometry?: unknown
313
+ } | null
314
+ }
315
+
316
+ export type ContourLines = {
317
+ datum: string
318
+ intervalFt: number
319
+ source?: string
320
+ lines: { elevationFt: number; points: [number, number][] }[]
321
+ }
322
+
323
+ /**
324
+ * The dossier's USGS 3DEP contour lines (elevation.data.terrain.geometry —
325
+ * a FeatureCollection of LineStrings with `elevation_ft`), projected into
326
+ * the site frame. Consecutive points closer than 0.3 m are dropped (the
327
+ * 10 m grid draws smooth curves with more vertices than a plan needs).
328
+ * Null when the section carries no lines.
329
+ */
330
+ export function contourLinesFromDossier(dossier: Dossier, origin: LngLat): ContourLines | null {
331
+ const el = answered<ElevationData>(dossier, 'elevation')
332
+ const terrain = el?.terrain
333
+ const fc = terrain?.geometry as { type?: string; features?: unknown[] } | undefined
334
+ if (!terrain || !fc || !Array.isArray(fc.features) || fc.features.length === 0) return null
335
+ const lines: ContourLines['lines'] = []
336
+ for (const f of fc.features as {
337
+ geometry?: { type?: string; coordinates?: unknown }
338
+ properties?: { elevation_ft?: unknown }
339
+ }[]) {
340
+ const ft = f?.properties?.elevation_ft
341
+ if (typeof ft !== 'number' || !Number.isFinite(ft)) continue
342
+ const geom = f.geometry
343
+ const parts: unknown[][] = []
344
+ if (geom?.type === 'LineString' && Array.isArray(geom.coordinates))
345
+ parts.push(geom.coordinates as unknown[])
346
+ else if (geom?.type === 'MultiLineString' && Array.isArray(geom.coordinates))
347
+ for (const l of geom.coordinates as unknown[])
348
+ if (Array.isArray(l)) parts.push(l as unknown[])
349
+ for (const part of parts) {
350
+ const pts: [number, number][] = []
351
+ for (const ll of part) {
352
+ if (!isLngLat(ll)) continue
353
+ const p = planPointFromLngLat(origin, ll)
354
+ const last = pts[pts.length - 1]
355
+ if (last && Math.hypot(last[0] - p[0], last[1] - p[1]) < 0.3) continue
356
+ pts.push([Math.round(p[0] * 1000) / 1000, Math.round(p[1] * 1000) / 1000])
357
+ }
358
+ if (pts.length >= 2) lines.push({ elevationFt: ft, points: pts })
359
+ }
360
+ }
361
+ if (lines.length === 0) return null
362
+ const section = dossier.layers?.elevation
363
+ return {
364
+ datum: terrain.datum ?? 'NAVD88',
365
+ intervalFt: typeof terrain.contour_interval_ft === 'number' ? terrain.contour_interval_ft : 1,
366
+ source: `USGS 3DEP (~${terrain.source_resolution_m ?? 10} m) via Pascal Map${section?.source?.vintage ? ` (${section.source.vintage})` : ''}`,
367
+ lines,
368
+ }
369
+ }
370
+
371
+ /* ----------------------------------------------------------- zoning */
372
+
373
+ const FT = 0.3048
374
+
375
+ /**
376
+ * Setbacks from the zoning section, METRES — null when the code expresses
377
+ * a conditional rule (a null side) or the section did not answer: the
378
+ * default then stands and the `dimensional_note` prints beside it.
379
+ */
380
+ export function setbacksFromZoning(
381
+ z: ZoningData | null | undefined,
382
+ ): { front: number; side: number; rear: number } | null {
383
+ const s = z?.setbacks
384
+ if (!s) return null
385
+ const f = s.front_ft
386
+ const sd = s.side_ft
387
+ const r = s.rear_ft
388
+ if (typeof f !== 'number' || typeof sd !== 'number' || typeof r !== 'number') return null
389
+ return { front: f * FT, side: sd * FT, rear: r * FT }
390
+ }
391
+
392
+ /** The citation line for `setbacksSource`. */
393
+ export function setbacksCitation(z: ZoningData, source?: DossierSection['source']): string {
394
+ const sec = z.dimensional_source?.section
395
+ ? `${z.dimensional_source.section}`
396
+ : 'land development code'
397
+ const url = z.dimensional_source?.url ?? z.land_development_code_url ?? ''
398
+ return `Zoning ${z.district ?? ''} (${z.jurisdiction ?? 'jurisdiction'}) — ${sec}${url ? ` ${url}` : ''} — via Pascal Map${source?.vintage ? ` (${source.vintage})` : ''}`
399
+ }
400
+
401
+ /* ----------------------------------------------------------- facts */
402
+
403
+ const FACT_LAYERS = [
404
+ 'parcel',
405
+ 'flood',
406
+ 'code_basis',
407
+ 'zoning',
408
+ 'utilities',
409
+ 'soils',
410
+ 'wetlands',
411
+ 'structures',
412
+ 'elevation',
413
+ 'boundaries',
414
+ ] as const
415
+
416
+ /** A section's data with every geometry stripped (they go to the site polygon / overlays, not the record). */
417
+ function stripGeometry(data: unknown): Record<string, unknown> | undefined {
418
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return undefined
419
+ const out: Record<string, unknown> = {}
420
+ for (const [k, v] of Object.entries(data as Record<string, unknown>)) {
421
+ if (k === 'geometry') continue
422
+ if (v && typeof v === 'object' && !Array.isArray(v)) {
423
+ const inner = stripGeometry(v)
424
+ if (inner) out[k] = inner
425
+ } else out[k] = v
426
+ }
427
+ return out
428
+ }
429
+
430
+ /**
431
+ * What the site node keeps of the dossier: when it was assembled, the
432
+ * point and address it was evaluated at, every section's status +
433
+ * summary + source, and the data of the sections the plan set acts on
434
+ * (geometry-free). `parcel.adjacent_parcels` is dropped too — thirty
435
+ * neighbours' keys are the platform's, not the plan's.
436
+ */
437
+ export function siteFactsFromDossier(dossier: Dossier): SiteDossier {
438
+ const sections: SiteDossier['sections'] = {}
439
+ for (const [layer, s] of Object.entries(dossier.layers ?? {})) {
440
+ if (!s || typeof s !== 'object') continue
441
+ sections[layer] = {
442
+ status: s.status,
443
+ ...(s.summary ? { summary: s.summary } : {}),
444
+ ...(s.source
445
+ ? {
446
+ source: {
447
+ name: s.source.name,
448
+ kind: s.source.kind,
449
+ vintage: s.source.vintage,
450
+ attribution: s.source.attribution,
451
+ },
452
+ }
453
+ : {}),
454
+ ...(s.reason ? { reason: s.reason } : {}),
455
+ }
456
+ }
457
+ const facts: SiteDossier = {
458
+ provider: 'Pascal Map',
459
+ asOf: dossier.as_of,
460
+ point: { lat: dossier.point?.lat, lng: dossier.point?.lng, source: dossier.point?.source },
461
+ ...(dossier.address
462
+ ? { address: { formatted: dossier.address.formatted, precision: dossier.address.precision } }
463
+ : {}),
464
+ sections,
465
+ }
466
+ const key: Record<(typeof FACT_LAYERS)[number], keyof SiteDossier> = {
467
+ parcel: 'parcel',
468
+ flood: 'flood',
469
+ code_basis: 'codeBasis',
470
+ zoning: 'zoning',
471
+ utilities: 'utilities',
472
+ soils: 'soils',
473
+ wetlands: 'wetlands',
474
+ structures: 'structures',
475
+ elevation: 'elevation',
476
+ boundaries: 'boundaries',
477
+ }
478
+ for (const layer of FACT_LAYERS) {
479
+ const data = answered(dossier, layer)
480
+ if (!data) continue
481
+ const stripped = stripGeometry(data)
482
+ if (!stripped) continue
483
+ if (layer === 'parcel') delete stripped.adjacent_parcels
484
+ ;(facts as Record<string, unknown>)[key[layer]] = stripped
485
+ }
486
+ return facts
487
+ }
488
+
489
+ /** One line for the status row: which sections answered. */
490
+ export function describeDossier(dossier: Dossier): string {
491
+ const yes: string[] = []
492
+ const no: string[] = []
493
+ for (const [layer, s] of Object.entries(dossier.layers ?? {})) {
494
+ if (s.status === 'available' || s.status === 'empty') yes.push(layer)
495
+ else no.push(layer)
496
+ }
497
+ return `Pascal Map: ${yes.length} sections answered${no.length ? ` (${no.join(', ')} not here)` : ''}`
498
+ }