@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,58 @@
1
+ 'use client'
2
+
3
+ import { Pentagon, Square } from 'lucide-react'
4
+ import { cn } from '../../../lib/utils'
5
+
6
+ export type OutlineShape = 'rectangle' | 'polygon'
7
+
8
+ const SHAPES = [
9
+ { id: 'rectangle', label: 'Rectangle', Icon: Square },
10
+ { id: 'polygon', label: 'Polygon', Icon: Pentagon },
11
+ ] as const
12
+
13
+ const pill =
14
+ 'inline-flex items-center gap-1 rounded-full border border-border/50 px-2 py-0.5 text-xs text-foreground transition-colors hover:bg-accent disabled:pointer-events-none disabled:opacity-50'
15
+
16
+ /**
17
+ * The outline shape, picked before drawing starts — the same two choices the
18
+ * Build panel's room tiles offer, so a cut or a mezzanine is started the way a
19
+ * room is.
20
+ */
21
+ export function ShapeChoice({
22
+ label,
23
+ onPick,
24
+ disabled,
25
+ className,
26
+ data,
27
+ }: {
28
+ label: string
29
+ onPick: (shape: OutlineShape) => void
30
+ disabled?: boolean
31
+ className?: string
32
+ /** `data-shape-choice` hook for tests and proofs. */
33
+ data?: string
34
+ }) {
35
+ return (
36
+ <div
37
+ className={cn('flex min-h-7 items-center justify-between gap-2', className)}
38
+ data-shape-choice={data}
39
+ >
40
+ <span className="whitespace-nowrap">{label}</span>
41
+ <div className="flex items-center gap-1.5">
42
+ {SHAPES.map(({ id, label: shapeLabel, Icon }) => (
43
+ <button
44
+ className={pill}
45
+ data-shape={id}
46
+ disabled={disabled}
47
+ key={id}
48
+ onClick={() => onPick(id)}
49
+ type="button"
50
+ >
51
+ <Icon aria-hidden className="h-3 w-3" />
52
+ {shapeLabel}
53
+ </button>
54
+ ))}
55
+ </div>
56
+ </div>
57
+ )
58
+ }
@@ -0,0 +1,109 @@
1
+ 'use client'
2
+
3
+ import {
4
+ type AnyNodeId,
5
+ useLiveNodeOverrides,
6
+ useScene,
7
+ type WallFaceRegion,
8
+ } from '@pascal-app/core'
9
+ import { useViewer } from '@pascal-app/viewer'
10
+ import { Trash2 } from 'lucide-react'
11
+ import { useMemo } from 'react'
12
+ import { removeWallRegion } from '../../../lib/paint-regions'
13
+ import { sfxEmitter } from '../../../lib/sfx-bus'
14
+ import { cn } from '../../../lib/utils'
15
+ import {
16
+ wallRegionCapNotice,
17
+ wallRegionFaceLabels,
18
+ wallRegionRows,
19
+ } from '../../../lib/wall-region-list'
20
+ import { useWallRegionSelection } from '../../../store/use-wall-region-handles-selection'
21
+ import { PanelSection } from '../controls/panel-section'
22
+
23
+ const ROW_ACTION_CLASS =
24
+ 'flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-black/5 hover:text-foreground dark:hover:bg-white/10'
25
+
26
+ /** The wall panel's "Paint regions" section: one row per region, select or delete. */
27
+ export function WallPaintRegionList({ wallId }: { wallId: string }) {
28
+ const nodes = useScene((state) => state.nodes)
29
+ const materials = useScene((state) => state.materials)
30
+ const unit = useViewer((state) => state.unit)
31
+ const active = useWallRegionSelection((state) => state.active)
32
+ const setActive = useWallRegionSelection((state) => state.setActive)
33
+ // A handle drag previews through a live override; list the live bounds.
34
+ const liveRegions = useLiveNodeOverrides(
35
+ (state) =>
36
+ state.overrides.get(wallId as AnyNodeId)?.faceRegions as WallFaceRegion[] | undefined,
37
+ )
38
+ const sceneWall = nodes[wallId as AnyNodeId]
39
+ const wall = sceneWall?.type === 'wall' ? sceneWall : null
40
+ const faceLabels = useMemo(() => wallRegionFaceLabels(nodes, wallId), [nodes, wallId])
41
+
42
+ if (!wall) return null
43
+ const faceRegions = liveRegions ?? wall.faceRegions
44
+ if (!faceRegions?.length) return null
45
+ const rows = wallRegionRows({ faceRegions }, faceLabels, materials, unit)
46
+ const capNotice = wallRegionCapNotice({ faceRegions }, faceLabels)
47
+
48
+ return (
49
+ // Focus inside this list still lets Delete target the active region.
50
+ <div className="contents" data-wall-region-list={wallId}>
51
+ <PanelSection title="Paint regions">
52
+ <div className="flex flex-col gap-1">
53
+ {rows.map((row) => {
54
+ const isActive = active?.wallId === wallId && active.regionId === row.id
55
+ return (
56
+ <div
57
+ className={cn(
58
+ 'flex h-9 items-center gap-2 rounded-md px-1 transition-colors',
59
+ isActive ? 'bg-accent' : 'hover:bg-accent/50',
60
+ )}
61
+ data-wall-region-row={row.id}
62
+ key={row.id}
63
+ >
64
+ <button
65
+ aria-pressed={isActive}
66
+ className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-left"
67
+ onClick={() => setActive({ wallId, regionId: row.id })}
68
+ type="button"
69
+ >
70
+ <span
71
+ aria-hidden="true"
72
+ className="h-4 w-4 shrink-0 rounded-sm border border-border/50 bg-center bg-cover"
73
+ style={{
74
+ backgroundColor: row.finish.color,
75
+ backgroundImage: row.finish.imageUrl
76
+ ? `url(${row.finish.imageUrl})`
77
+ : undefined,
78
+ }}
79
+ />
80
+ <span className="flex min-w-0 flex-col leading-tight">
81
+ <span className="truncate text-sm">{row.finish.name}</span>
82
+ <span className="truncate text-[11px] text-muted-foreground">
83
+ {row.faceLabel} · {row.boundsText}
84
+ </span>
85
+ </span>
86
+ </button>
87
+ <button
88
+ aria-label="Delete region"
89
+ className={ROW_ACTION_CLASS}
90
+ onClick={() => {
91
+ if (isActive) setActive(null)
92
+ if (removeWallRegion(wallId, row.id)) sfxEmitter.emit('sfx:structure-delete')
93
+ }}
94
+ title="Delete region"
95
+ type="button"
96
+ >
97
+ <Trash2 className="h-3 w-3" />
98
+ </button>
99
+ </div>
100
+ )
101
+ })}
102
+ {capNotice && (
103
+ <div className="px-1 text-[11px] text-muted-foreground">{capNotice}</div>
104
+ )}
105
+ </div>
106
+ </PanelSection>
107
+ </div>
108
+ )
109
+ }
@@ -0,0 +1,8 @@
1
+ /** A small mark beside a name: the plan a plugin needs ("Pro"), as the plugin manager shows it. */
2
+ export function Badge({ label }: { label: string }) {
3
+ return (
4
+ <span className="rounded-[5px] border border-border px-1.5 py-px font-mono font-medium text-[10px] text-muted-foreground uppercase tracking-wide">
5
+ {label}
6
+ </span>
7
+ )
8
+ }
@@ -1,43 +1,55 @@
1
1
  'use client'
2
2
 
3
+ import { ZONE_COLOR_PALETTE } from '@pascal-app/core'
3
4
  import { useState } from 'react'
4
5
  import { cn } from '../../../lib/utils'
5
6
  import { Popover, PopoverContent, PopoverTrigger } from './popover'
7
+ import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip'
6
8
 
7
- export const PALETTE_COLORS = [
8
- '#ef4444', // Red 0°
9
- '#f97316', // Orange 30°
10
- '#f59e0b', // Amber 45°
11
- '#84cc16', // Lime 85°
12
- '#22c55e', // Green 142°
13
- '#10b981', // Emerald 160°
14
- '#06b6d4', // Cyan 190°
15
- '#3b82f6', // Blue 217°
16
- '#6366f1', // Indigo 239°
17
- '#a855f7', // Violet 270°
18
- '#64748b', // Dark gray
19
- '#cccccc', // Light gray
20
- ]
9
+ export const PALETTE_COLORS = ZONE_COLOR_PALETTE
21
10
 
22
11
  interface ColorDotProps {
23
12
  color: string
24
13
  onChange: (color: string) => void
14
+ /** Says what the colour is for, on hover and to assistive tech. */
15
+ label?: string
16
+ side?: 'left' | 'bottom'
25
17
  }
26
18
 
27
- export function ColorDot({ color, onChange }: ColorDotProps) {
19
+ export function ColorDot({ color, onChange, label, side = 'left' }: ColorDotProps) {
28
20
  const [open, setOpen] = useState(false)
21
+ const trigger = (
22
+ <PopoverTrigger asChild>
23
+ <button
24
+ aria-label={label}
25
+ className="relative h-3 w-3 shrink-0 cursor-pointer rounded-sm border border-border/50 transition-all hover:ring-1 hover:ring-ring/50"
26
+ data-color-dot
27
+ onClick={(e) => e.stopPropagation()}
28
+ style={{ backgroundColor: color }}
29
+ type="button"
30
+ />
31
+ </PopoverTrigger>
32
+ )
29
33
 
30
34
  return (
31
35
  <Popover onOpenChange={setOpen} open={open}>
32
- <PopoverTrigger asChild>
33
- <button
34
- className="relative h-3 w-3 shrink-0 cursor-pointer rounded-sm border border-border/50 transition-all hover:ring-1 hover:ring-ring/50"
35
- onClick={(e) => e.stopPropagation()}
36
- style={{ backgroundColor: color }}
37
- type="button"
38
- />
39
- </PopoverTrigger>
40
- <PopoverContent align="center" className="w-auto p-1.5" side="left" sideOffset={6}>
36
+ {label ? (
37
+ <Tooltip>
38
+ <TooltipTrigger asChild>{trigger}</TooltipTrigger>
39
+ <TooltipContent side="bottom">{label}</TooltipContent>
40
+ </Tooltip>
41
+ ) : (
42
+ trigger
43
+ )}
44
+ <PopoverContent
45
+ align={side === 'bottom' ? 'start' : 'center'}
46
+ className="w-auto p-1.5"
47
+ // Keep the pick inside the palette: in a panel header, a pointerdown
48
+ // bubbling out of the portal would start dragging the panel.
49
+ onPointerDown={(event) => event.stopPropagation()}
50
+ side={side}
51
+ sideOffset={6}
52
+ >
41
53
  <div className="grid grid-cols-4 gap-1">
42
54
  {PALETTE_COLORS.map((c) => (
43
55
  <button
@@ -24,6 +24,7 @@ import {
24
24
  TooltipTrigger,
25
25
  } from './../../../components/ui/primitives/tooltip'
26
26
  import { useIsMobile } from './../../../hooks/use-mobile'
27
+ import { hydrationGatedStorage } from './../../../lib/hydration-gated-storage'
27
28
  import { cn } from './../../../lib/utils'
28
29
 
29
30
  const SIDEBAR_COOKIE_NAME = 'sidebar_state'
@@ -47,7 +48,7 @@ type SidebarStore = {
47
48
  export const useSidebarStore = create<SidebarStore>()(
48
49
  persist(
49
50
  (set) => ({
50
- width: 288, // 18rem = 288px
51
+ width: 360,
51
52
  setWidth: (width) => {
52
53
  if (width < SIDEBAR_COLLAPSE_THRESHOLD) {
53
54
  set({ isCollapsed: true })
@@ -62,6 +63,7 @@ export const useSidebarStore = create<SidebarStore>()(
62
63
  }),
63
64
  {
64
65
  name: 'sidebar-preferences',
66
+ storage: hydrationGatedStorage((): boolean => useSidebarStore.persist.hasHydrated()),
65
67
  partialize: (state) => ({ width: state.width, isCollapsed: state.isCollapsed }),
66
68
  skipHydration: true,
67
69
  },
@@ -0,0 +1,29 @@
1
+ import { expect, test } from 'bun:test'
2
+ import { rightStackFloors } from './right-stack'
3
+
4
+ const tall = { headerHeight: 52, cardHeight: 240 }
5
+
6
+ test('the inspector keeps 180 px, or its own height if shorter, above the full card', () => {
7
+ expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 700 })).toEqual({
8
+ inspector: 180,
9
+ card: 240,
10
+ })
11
+ expect(rightStackFloors({ ...tall, naturalHeight: 120, stackHeight: 700 }).inspector).toBe(120)
12
+ expect(
13
+ rightStackFloors({ naturalHeight: 0, headerHeight: 0, cardHeight: 240, stackHeight: 700 }),
14
+ ).toEqual({ inspector: 0, card: 240 })
15
+ })
16
+
17
+ test('on a short window the inspector gives way first, down to its header', () => {
18
+ expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 400 })).toEqual({
19
+ inspector: 152,
20
+ card: 240,
21
+ })
22
+ })
23
+
24
+ test('on a very short window the inspector keeps its header and the card scrolls', () => {
25
+ expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 258 })).toEqual({
26
+ inspector: 52,
27
+ card: 198,
28
+ })
29
+ })
@@ -0,0 +1,164 @@
1
+ 'use client'
2
+
3
+ import { createContext, type ReactNode, useContext, useEffect, useRef } from 'react'
4
+ import { useIsMobile } from '../../hooks/use-mobile'
5
+ import { cn } from '../../lib/utils'
6
+ import { TOP_MENU_CLEARANCE } from './action-menu/placement'
7
+
8
+ const RightStackContext = createContext(false)
9
+
10
+ /** Whether a panel renders inside the shared right column (in flow) rather than on its own. */
11
+ export function useInRightStack() {
12
+ return useContext(RightStackContext)
13
+ }
14
+
15
+ /** The inspector never shrinks below this (or its own height, if shorter) for the card, px. */
16
+ const INSPECTOR_MIN_HEIGHT = 180
17
+
18
+ /** Gap between the inspector and the card, px (`gap-2`). */
19
+ const GAP = 8
20
+
21
+ /**
22
+ * The heights neither panel shrinks below. The inspector keeps 180 px (or its
23
+ * own height, if shorter) while the full card fits under it, and never less
24
+ * than its header row. The card shows in full whenever the column allows; on a
25
+ * very short window it gives way and scrolls on itself — both stay usable.
26
+ */
27
+ export function rightStackFloors(args: {
28
+ naturalHeight: number
29
+ headerHeight: number
30
+ cardHeight: number
31
+ stackHeight: number
32
+ }): { inspector: number; card: number } {
33
+ const { naturalHeight, headerHeight, cardHeight, stackHeight } = args
34
+ const gap = naturalHeight > 0 && cardHeight > 0 ? GAP : 0
35
+ const header = Math.min(headerHeight, naturalHeight)
36
+ const inspector = Math.max(
37
+ header,
38
+ Math.min(INSPECTOR_MIN_HEIGHT, naturalHeight, stackHeight - cardHeight - gap),
39
+ )
40
+ const card = Math.max(0, Math.min(cardHeight, stackHeight - gap - inspector))
41
+ return { inspector, card }
42
+ }
43
+
44
+ /** The inspector's full height: its header plus everything its scroller holds. */
45
+ function inspectorHeights(slot: HTMLElement): { natural: number; header: number } {
46
+ const panel = slot.querySelector<HTMLElement>('[data-panel-wrapper]')
47
+ if (!panel) return { natural: 0, header: 0 }
48
+ const header = panel.querySelector<HTMLElement>('[data-panel-header]')?.offsetHeight ?? 0
49
+ const scroller = panel.querySelector<HTMLElement>('[data-panel-scroll]')
50
+ if (!scroller) return { natural: panel.offsetHeight, header }
51
+ return { natural: panel.offsetHeight - scroller.clientHeight + scroller.scrollHeight, header }
52
+ }
53
+
54
+ /**
55
+ * The right-hand column: the shortcuts card shows in full at the bottom, and
56
+ * the node inspector takes the space above it, growing down from the top and
57
+ * scrolling on itself. On a very short window the inspector keeps its header
58
+ * and the card scrolls instead. Neither hides the other. On a phone the two
59
+ * keep their own layouts (sheet / no card).
60
+ */
61
+ export function RightStack({
62
+ inspector,
63
+ helper,
64
+ reserveBottomMenu = true,
65
+ belowTopMenu = false,
66
+ }: {
67
+ inspector: ReactNode
68
+ helper: ReactNode
69
+ // Keep clear of an action menu docked at the bottom; off when it sits at the top.
70
+ reserveBottomMenu?: boolean
71
+ // Start below an action menu docked at the top of the stage.
72
+ belowTopMenu?: boolean
73
+ }) {
74
+ const isMobile = useIsMobile()
75
+ const stackRef = useRef<HTMLDivElement>(null)
76
+ const inspectorRef = useRef<HTMLDivElement>(null)
77
+ const cardRef = useRef<HTMLDivElement>(null)
78
+
79
+ // `min(180px, its own height)` has no CSS form for a flex item that scrolls,
80
+ // so both floors are measured (`rightStackFloors`).
81
+ useEffect(() => {
82
+ if (isMobile) return
83
+ const stack = stackRef.current
84
+ const slot = inspectorRef.current
85
+ const card = cardRef.current
86
+ if (!(stack && slot && card)) return
87
+ let frame = 0
88
+ const fit = () => {
89
+ frame = 0
90
+ const { natural, header } = inspectorHeights(slot)
91
+ const floors = rightStackFloors({
92
+ naturalHeight: natural,
93
+ headerHeight: header,
94
+ cardHeight: card.scrollHeight,
95
+ stackHeight: stack.clientHeight,
96
+ })
97
+ const inspectorMin = `${floors.inspector}px`
98
+ const cardMin = `${floors.card}px`
99
+ if (slot.style.minHeight !== inspectorMin) slot.style.minHeight = inspectorMin
100
+ if (card.style.minHeight !== cardMin) card.style.minHeight = cardMin
101
+ // The card is click-through; it takes the pointer only when it has to scroll.
102
+ const pointer = floors.card < card.scrollHeight ? 'auto' : ''
103
+ if (card.style.pointerEvents !== pointer) card.style.pointerEvents = pointer
104
+ }
105
+ const schedule = () => {
106
+ if (!frame) frame = requestAnimationFrame(fit)
107
+ }
108
+ const resize = new ResizeObserver(schedule)
109
+ resize.observe(stack)
110
+ resize.observe(card)
111
+ // Content growing inside the scroller changes no outer size, hence the mutation watch.
112
+ // The same for the card: a new tool swaps its rows without resizing the slot.
113
+ const mutation = new MutationObserver(schedule)
114
+ for (const target of [slot, card])
115
+ mutation.observe(target, {
116
+ subtree: true,
117
+ childList: true,
118
+ attributes: true,
119
+ attributeFilter: ['class', 'hidden', 'aria-expanded'],
120
+ characterData: true,
121
+ })
122
+ fit()
123
+ return () => {
124
+ if (frame) cancelAnimationFrame(frame)
125
+ resize.disconnect()
126
+ mutation.disconnect()
127
+ }
128
+ }, [isMobile])
129
+
130
+ if (isMobile) {
131
+ return (
132
+ <>
133
+ <div className="pointer-events-auto">{inspector}</div>
134
+ <div className="pointer-events-auto">{helper}</div>
135
+ </>
136
+ )
137
+ }
138
+
139
+ return (
140
+ <RightStackContext.Provider value={true}>
141
+ <div
142
+ className={cn(
143
+ 'pointer-events-none fixed right-4 z-40 flex flex-col items-end gap-2',
144
+ reserveBottomMenu ? 'bottom-[74px]' : 'bottom-4',
145
+ !belowTopMenu && 'top-20',
146
+ )}
147
+ style={belowTopMenu ? { top: TOP_MENU_CLEARANCE } : undefined}
148
+ data-right-stack
149
+ ref={stackRef}
150
+ >
151
+ <div className="flex min-h-0 flex-[0_1_auto] flex-col items-end" ref={inspectorRef}>
152
+ {inspector}
153
+ </div>
154
+ <div
155
+ className="no-scrollbar mt-auto flex min-h-0 flex-[0_1_auto] flex-col overflow-y-auto rounded-xl"
156
+ data-right-stack-card
157
+ ref={cardRef}
158
+ >
159
+ {helper}
160
+ </div>
161
+ </div>
162
+ </RightStackContext.Provider>
163
+ )
164
+ }
@@ -78,7 +78,7 @@ export function AppSidebar({
78
78
 
79
79
  return (
80
80
  <>
81
- <div className={cn('dark flex h-full w-full bg-sidebar text-sidebar-foreground')}>
81
+ <div className="flex h-full w-full bg-sidebar text-sidebar-foreground">
82
82
  {/* Icon Rail */}
83
83
  <IconRail
84
84
  activePanel={activePanel}
@@ -17,6 +17,11 @@ export type ExtraPanel = {
17
17
  label: string
18
18
  component: ComponentType
19
19
  pluginId?: string
20
+ /** Rail entry that drives the stage (see `SidebarTab.noPanel`); `component` serves the v1 sidebar. */
21
+ noPanel?: boolean
22
+ onSelect?: () => void
23
+ stageActive?: boolean
24
+ onDeselect?: () => void
20
25
  }
21
26
 
22
27
  interface IconRailProps {
@@ -60,7 +65,7 @@ export function IconRail({
60
65
  <button
61
66
  className={cn(
62
67
  'flex h-9 w-9 items-center justify-center rounded-lg transition-all',
63
- isActive ? 'bg-accent' : 'hover:bg-accent',
68
+ isActive ? 'bg-sidebar-accent' : 'hover:bg-sidebar-accent',
64
69
  )}
65
70
  onClick={() => onPanelChange(panel.id)}
66
71
  type="button"
@@ -102,7 +107,7 @@ export function IconRail({
102
107
  <button
103
108
  className={cn(
104
109
  'flex h-9 w-9 items-center justify-center rounded-lg transition-all',
105
- isActive ? 'bg-accent' : 'hover:bg-accent',
110
+ isActive ? 'bg-sidebar-accent' : 'hover:bg-sidebar-accent',
106
111
  )}
107
112
  onClick={() => onPanelChange(panel.id)}
108
113
  type="button"
@@ -133,7 +138,7 @@ export function IconRail({
133
138
  <button
134
139
  className={cn(
135
140
  'flex h-9 w-9 items-center justify-center rounded-lg transition-all',
136
- isActive ? 'bg-accent' : 'hover:bg-accent',
141
+ isActive ? 'bg-sidebar-accent' : 'hover:bg-sidebar-accent',
137
142
  )}
138
143
  onClick={() => onPanelChange(panel.id)}
139
144
  type="button"
@@ -136,7 +136,7 @@ export function FunctionTreePanel({
136
136
  'relative flex aspect-square items-center justify-center rounded-xl transition-all duration-200',
137
137
  isActive
138
138
  ? 'bg-primary/10 ring-1 ring-primary/50'
139
- : 'bg-muted/40 opacity-70 grayscale hover:bg-muted hover:opacity-100 hover:grayscale-0',
139
+ : 'bg-black/4 opacity-70 grayscale hover:bg-black/8 hover:opacity-100 hover:grayscale-0 dark:bg-muted/40 dark:hover:bg-muted',
140
140
  )}
141
141
  onClick={() => {
142
142
  triggerSFX('sfx:menu-click')
@@ -279,7 +279,7 @@ function LegacyItemsPanel({
279
279
  'cursor-pointer rounded-md px-2 py-0.5 font-medium text-xs transition-colors',
280
280
  activePlacementTag === null
281
281
  ? 'bg-blue-500 text-white'
282
- : 'bg-blue-950/50 text-blue-300 hover:bg-blue-900/60 hover:text-blue-200',
282
+ : 'bg-blue-50 dark:bg-blue-950/50 text-blue-700 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/60 hover:text-blue-800 dark:hover:text-blue-200',
283
283
  )}
284
284
  onClick={() => setActivePlacementTag(null)}
285
285
  type="button"
@@ -297,8 +297,8 @@ function LegacyItemsPanel({
297
297
  isActive
298
298
  ? 'bg-blue-500 text-white'
299
299
  : isEmpty
300
- ? 'cursor-not-allowed bg-zinc-800 text-zinc-500'
301
- : 'bg-blue-950/50 text-blue-300 hover:bg-blue-900/60 hover:text-blue-200',
300
+ ? 'cursor-not-allowed bg-zinc-100 dark:bg-zinc-800 text-zinc-400 dark:text-zinc-500'
301
+ : 'bg-blue-50 dark:bg-blue-950/50 text-blue-700 dark:text-blue-300 hover:bg-blue-100 dark:hover:bg-blue-900/60 hover:text-blue-800 dark:hover:text-blue-200',
302
302
  )}
303
303
  disabled={isEmpty}
304
304
  key={tag}
@@ -312,7 +312,7 @@ function LegacyItemsPanel({
312
312
  isActive
313
313
  ? 'text-blue-200'
314
314
  : isEmpty
315
- ? 'text-zinc-600'
315
+ ? 'text-zinc-400 dark:text-zinc-600'
316
316
  : 'text-blue-500/70',
317
317
  )}
318
318
  >
@@ -337,7 +337,7 @@ function LegacyItemsPanel({
337
337
  isActive
338
338
  ? 'bg-violet-500 text-white'
339
339
  : isEmpty
340
- ? 'cursor-not-allowed bg-zinc-800 text-zinc-500'
340
+ ? 'cursor-not-allowed bg-zinc-100 dark:bg-zinc-800 text-zinc-400 dark:text-zinc-500'
341
341
  : 'bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground',
342
342
  )}
343
343
  disabled={isEmpty}
@@ -352,7 +352,7 @@ function LegacyItemsPanel({
352
352
  isActive
353
353
  ? 'text-violet-200'
354
354
  : isEmpty
355
- ? 'text-zinc-600'
355
+ ? 'text-zinc-400 dark:text-zinc-600'
356
356
  : 'text-zinc-500/70',
357
357
  )}
358
358
  >
@@ -0,0 +1,40 @@
1
+ 'use client'
2
+
3
+ import { ANIMATION_SPEEDS, type AnimationSpeed, normalizeAnimationSpeed } from '@pascal-app/viewer'
4
+ import useConstructionReveal from '../../../../../store/use-construction-reveal'
5
+ import { SegmentedControl } from '../../../controls/segmented-control'
6
+
7
+ const label = (speed: AnimationSpeed) => `×${speed}`
8
+
9
+ // The control's values are strings; a step is its number as text.
10
+ const OPTIONS = ANIMATION_SPEEDS.map((speed) => ({
11
+ label: label(speed),
12
+ value: String(speed),
13
+ title: speed === 1 ? 'The pace builds were made at.' : `Builds play ×${speed} as fast.`,
14
+ }))
15
+
16
+ /** How fast what the AI builds plays: ×0.5 to ×2, for the Full and Framing animations and their undo. */
17
+ export function AnimationSpeedSetting() {
18
+ const level = useConstructionReveal((state) => state.level)
19
+ const speed = useConstructionReveal((state) => state.speed)
20
+ const setSpeed = useConstructionReveal((state) => state.setSpeed)
21
+ const off = level === 'off'
22
+ return (
23
+ <div className="space-y-2 py-1">
24
+ <div className="space-y-0.5">
25
+ <p className="font-medium text-sm">Animation speed</p>
26
+ <p className="text-muted-foreground text-xs">
27
+ {off
28
+ ? 'Turn the construction animation on to use this.'
29
+ : 'Higher plays a build faster; it applies to the next build.'}
30
+ </p>
31
+ </div>
32
+ <SegmentedControl
33
+ aria-label="Animation speed"
34
+ onChange={(value) => setSpeed(normalizeAnimationSpeed(Number(value)))}
35
+ options={OPTIONS}
36
+ value={String(speed)}
37
+ />
38
+ </div>
39
+ )
40
+ }