@pascal-app/editor 1.0.1 → 1.0.4

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 (498) hide show
  1. package/package.json +16 -9
  2. package/src/components/editor/action-menu-button.tsx +83 -0
  3. package/src/components/editor/bake-thumbnail.tsx +315 -4
  4. package/src/components/editor/capture-level-picker.tsx +72 -0
  5. package/src/components/editor/custom-camera-controls.tsx +221 -128
  6. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  7. package/src/components/editor/duplicate-node.ts +244 -0
  8. package/src/components/editor/editor-layout-mobile.tsx +37 -25
  9. package/src/components/editor/editor-layout-v2.tsx +131 -58
  10. package/src/components/editor/export-manager.tsx +34 -8
  11. package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
  12. package/src/components/editor/first-person/build-collider-world.ts +6 -2
  13. package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
  14. package/src/components/editor/first-person/mechanism-targets.ts +36 -0
  15. package/src/components/editor/first-person-controls.tsx +205 -2
  16. package/src/components/editor/floating-action-menu.tsx +63 -207
  17. package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
  18. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  19. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  20. package/src/components/editor/floor-region-controls.tsx +846 -0
  21. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  22. package/src/components/editor/floor-surface.test.ts +165 -0
  23. package/src/components/editor/floor-surface.ts +182 -0
  24. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  25. package/src/components/editor/floorplan-background-selection.ts +1 -3
  26. package/src/components/editor/floorplan-panel.tsx +536 -487
  27. package/src/components/editor/footprint-height-handle.tsx +204 -0
  28. package/src/components/editor/grid.tsx +31 -7
  29. package/src/components/editor/group-actions.ts +59 -11
  30. package/src/components/editor/group-floating-action-menu.tsx +19 -9
  31. package/src/components/editor/group-move-3d.ts +4 -6
  32. package/src/components/editor/group-rotate-handle.tsx +81 -35
  33. package/src/components/editor/group-selection-box-3d.tsx +25 -17
  34. package/src/components/editor/group-transform-shared.test.ts +126 -1
  35. package/src/components/editor/group-transform-shared.ts +79 -73
  36. package/src/components/editor/handles/handle-arrow.tsx +72 -2
  37. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  38. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  39. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  40. package/src/components/editor/handles/use-handle-drag.ts +272 -131
  41. package/src/components/editor/index.tsx +316 -176
  42. package/src/components/editor/join-walls-pill.tsx +44 -0
  43. package/src/components/editor/node-action-menu.tsx +70 -70
  44. package/src/components/editor/node-arrow-handles.tsx +167 -14
  45. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  46. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  47. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  48. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  49. package/src/components/editor/registry-action-contributions.tsx +132 -0
  50. package/src/components/editor/room-controls.tsx +847 -0
  51. package/src/components/editor/room-floor-highlight.tsx +128 -0
  52. package/src/components/editor/room-handles-2d.tsx +354 -0
  53. package/src/components/editor/room-handles.tsx +675 -0
  54. package/src/components/editor/room-highlight.tsx +225 -0
  55. package/src/components/editor/room-labels.test.ts +357 -0
  56. package/src/components/editor/room-labels.tsx +990 -0
  57. package/src/components/editor/room-transform-ghost.tsx +256 -0
  58. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  59. package/src/components/editor/selection-manager.tsx +451 -317
  60. package/src/components/editor/slab-hole-highlights.tsx +3 -0
  61. package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
  62. package/src/components/editor/snapshot-capture.test.ts +52 -4
  63. package/src/components/editor/snapshot-capture.ts +30 -5
  64. package/src/components/editor/thumbnail-generator.test.ts +308 -0
  65. package/src/components/editor/thumbnail-generator.tsx +1035 -229
  66. package/src/components/editor/use-floorplan-background-placement.ts +31 -11
  67. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  68. package/src/components/editor/views/host-view.tsx +61 -0
  69. package/src/components/editor/views/plugins-view.tsx +670 -0
  70. package/src/components/editor/views/scene-region.tsx +59 -0
  71. package/src/components/editor/views/use-editor-views.ts +57 -0
  72. package/src/components/editor/views/view-bar.tsx +220 -0
  73. package/src/components/editor/wall-justification.test.ts +140 -0
  74. package/src/components/editor/wall-move-side-handles.tsx +114 -163
  75. package/src/components/editor/wall-opening-highlights.tsx +2 -3
  76. package/src/components/editor/wall-push-arrow.tsx +92 -0
  77. package/src/components/editor/wall-region-handles.tsx +509 -0
  78. package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
  79. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  80. package/src/components/editor-2d/floorplan-group-move.tsx +17 -24
  81. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  82. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  83. package/src/components/editor-2d/floorplan-registered-tool-layer.tsx +33 -12
  84. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  85. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +242 -126
  86. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  87. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  88. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  89. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +166 -0
  90. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  91. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +87 -8
  92. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +355 -245
  93. package/src/components/editor-2d/svg-paths.ts +12 -15
  94. package/src/components/procedural-items/inspector.tsx +40 -15
  95. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  96. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  97. package/src/components/systems/zone/zone-system.test.ts +47 -0
  98. package/src/components/systems/zone/zone-system.tsx +31 -66
  99. package/src/components/tools/__fixtures__/hosting.ts +10 -0
  100. package/src/components/tools/item/item-surface-placement.test.ts +3 -9
  101. package/src/components/tools/item/item-surface-strategy-resolver.test.ts +3 -9
  102. package/src/components/tools/item/placement-history.test.tsx +474 -0
  103. package/src/components/tools/item/placement-strategies.ts +63 -47
  104. package/src/components/tools/item/shelf-surface-strategy-resolver.test.ts +3 -3
  105. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  106. package/src/components/tools/item/use-draft-node.ts +207 -97
  107. package/src/components/tools/item/use-placement-coordinator.tsx +251 -111
  108. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  109. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  110. package/src/components/tools/registry/item-surface-move.test.ts +10 -2
  111. package/src/components/tools/registry/item-surface-move.ts +46 -24
  112. package/src/components/tools/registry/item-surface-resolver.test.ts +2 -2
  113. package/src/components/tools/registry/move-registry-node-tool.tsx +26 -10
  114. package/src/components/tools/select/box-select-tool.tsx +2 -12
  115. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  116. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  117. package/src/components/tools/select/select-candidates.test.ts +37 -4
  118. package/src/components/tools/select/select-candidates.ts +15 -11
  119. package/src/components/tools/shared/affordance-dispatch.test.ts +24 -0
  120. package/src/components/tools/shared/affordance-dispatch.ts +13 -0
  121. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  122. package/src/components/tools/shared/draft-measurement-label.tsx +38 -0
  123. package/src/components/tools/shared/pointer-support-cap.test.ts +27 -0
  124. package/src/components/tools/shared/pointer-support-cap.ts +17 -5
  125. package/src/components/tools/shared/surface-rejection.test.tsx +5 -4
  126. package/src/components/tools/shared/surface-rejection.tsx +14 -0
  127. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  128. package/src/components/tools/stair/stair-defaults.ts +0 -3
  129. package/src/components/tools/stair/stair-tool.tsx +90 -42
  130. package/src/components/tools/tool-manager.tsx +82 -23
  131. package/src/components/tools/wall/wall-drafting.test.ts +19 -4
  132. package/src/components/tools/zone/zone-tool.tsx +4 -12
  133. package/src/components/ui/action-menu/action-button.tsx +3 -0
  134. package/src/components/ui/action-menu/camera-actions.tsx +2 -1
  135. package/src/components/ui/action-menu/control-modes.tsx +2 -35
  136. package/src/components/ui/action-menu/measurement-control.tsx +3 -10
  137. package/src/components/ui/build-panel/build-panel.tsx +235 -0
  138. package/src/components/ui/command-palette/editor-commands.tsx +4 -30
  139. package/src/components/ui/command-palette/index.tsx +5 -24
  140. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  141. package/src/components/ui/controls/material-paint-panel.tsx +101 -91
  142. package/src/components/ui/controls/material-picker.tsx +8 -67
  143. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  144. package/src/components/ui/controls/select-control.tsx +65 -0
  145. package/src/components/ui/controls/slider-control.tsx +76 -31
  146. package/src/components/ui/floating-level-selector.tsx +9 -14
  147. package/src/components/ui/helpers/building-helper.tsx +4 -1
  148. package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
  149. package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
  150. package/src/components/ui/helpers/helper-manager.tsx +348 -19
  151. package/src/components/ui/helpers/item-helper.tsx +8 -0
  152. package/src/components/ui/helpers/registered-tool-helper.tsx +10 -33
  153. package/src/components/ui/icon-ref.tsx +45 -1
  154. package/src/components/ui/item-catalog/item-catalog.tsx +5 -30
  155. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  156. package/src/components/ui/node-icon-image.tsx +16 -0
  157. package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
  158. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  159. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  160. package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
  161. package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
  162. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  163. package/src/components/ui/panels/multi-field-value.ts +6 -3
  164. package/src/components/ui/panels/multi-height-mode.tsx +27 -21
  165. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  166. package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
  167. package/src/components/ui/panels/node-display.ts +8 -3
  168. package/src/components/ui/panels/panel-manager.tsx +103 -68
  169. package/src/components/ui/panels/panel-wrapper.tsx +78 -36
  170. package/src/components/ui/panels/parametric-field-control.tsx +1 -1
  171. package/src/components/ui/panels/parametric-inspector.tsx +7 -19
  172. package/src/components/ui/panels/reference-panel.tsx +2 -3
  173. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  174. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  175. package/src/components/ui/panels/room-name-options.ts +47 -0
  176. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  177. package/src/components/ui/panels/room-panel.tsx +725 -0
  178. package/src/components/ui/panels/shape-choice.tsx +58 -0
  179. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  180. package/src/components/ui/primitives/color-dot.tsx +36 -24
  181. package/src/components/ui/primitives/shortcut-token.tsx +4 -0
  182. package/src/components/ui/primitives/sidebar.tsx +2 -0
  183. package/src/components/ui/right-stack.test.ts +29 -0
  184. package/src/components/ui/right-stack.tsx +145 -0
  185. package/src/components/ui/sidebar/icon-rail.tsx +5 -0
  186. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  187. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +615 -372
  188. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +7 -7
  189. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  190. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  191. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  192. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  193. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  194. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  195. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  196. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  197. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  198. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  199. package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
  200. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  201. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  202. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  203. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  204. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  205. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  206. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  207. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  208. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  209. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  210. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
  211. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  212. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  213. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  214. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  215. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
  216. package/src/components/ui/sidebar/tab-bar.tsx +10 -0
  217. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  218. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  219. package/src/components/viewer/floorplan-preview-visibility.test.ts +35 -0
  220. package/src/components/viewer/floorplan-preview-visibility.ts +19 -0
  221. package/src/components/viewer/floorplan-preview.tsx +6 -18
  222. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  223. package/src/components/viewer/viewer-rooms.tsx +175 -0
  224. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  225. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  226. package/src/components/viewer/viewer-stage.tsx +2 -2
  227. package/src/components/viewer-zone-system.tsx +9 -9
  228. package/src/hooks/use-auto-save.integration.test.tsx +311 -0
  229. package/src/hooks/use-auto-save.ts +55 -14
  230. package/src/hooks/use-ceiling-events.test.ts +1 -0
  231. package/src/hooks/use-ceiling-events.ts +2 -0
  232. package/src/hooks/use-grid-events.test.ts +2 -0
  233. package/src/hooks/use-grid-events.ts +16 -11
  234. package/src/hooks/use-keyboard.test.ts +182 -0
  235. package/src/hooks/use-keyboard.ts +122 -91
  236. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  237. package/src/hooks/use-open-wall-ends.ts +238 -0
  238. package/src/hooks/use-placement-notice.test.ts +48 -0
  239. package/src/hooks/use-placement-notice.ts +42 -0
  240. package/src/hooks/use-selected-room.ts +143 -0
  241. package/src/index.tsx +306 -39
  242. package/src/lib/catalog-panel-model.ts +39 -0
  243. package/src/lib/catalog-selection.test.ts +27 -0
  244. package/src/lib/catalog-selection.ts +8 -0
  245. package/src/lib/ceiling-edit-session.test.ts +321 -0
  246. package/src/lib/ceiling-edit-session.ts +206 -0
  247. package/src/lib/ceiling-region-session.test.ts +179 -0
  248. package/src/lib/contextual-help.test.ts +27 -1
  249. package/src/lib/contextual-help.ts +16 -9
  250. package/src/lib/continuation.test.ts +19 -1
  251. package/src/lib/continuation.ts +25 -4
  252. package/src/lib/door-interaction.ts +14 -0
  253. package/src/lib/editor-level-display.test.ts +96 -0
  254. package/src/lib/editor-level-display.ts +48 -0
  255. package/src/lib/editor-views.ts +144 -0
  256. package/src/lib/export-geometry-wait.test.ts +90 -0
  257. package/src/lib/floor-edit-session.ts +153 -0
  258. package/src/lib/floor-footprints.test.ts +563 -0
  259. package/src/lib/floor-footprints.ts +395 -0
  260. package/src/lib/floor-foundation-support.test.ts +283 -0
  261. package/src/lib/floor-opening-draft.ts +218 -0
  262. package/src/lib/floor-openings.test.ts +216 -0
  263. package/src/lib/floor-region-geometry.ts +191 -0
  264. package/src/lib/floor-region-session.test.ts +485 -0
  265. package/src/lib/floor-region-session.ts +589 -0
  266. package/src/lib/floor-region-snap.ts +101 -0
  267. package/src/lib/floor-step-hover.test.ts +32 -0
  268. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  269. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  270. package/src/lib/floorplan/dimension-drive.ts +479 -0
  271. package/src/lib/floorplan/floorplan-export.test.ts +15 -9
  272. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  273. package/src/lib/floorplan/floorplan-extension.ts +48 -0
  274. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  275. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  276. package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
  277. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
  278. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
  279. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  280. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  281. package/src/lib/floorplan/geometry.ts +13 -6
  282. package/src/lib/floorplan/index.ts +0 -9
  283. package/src/lib/floorplan/items.ts +5 -2
  284. package/src/lib/floorplan/marks.test.ts +213 -0
  285. package/src/lib/floorplan/marks.ts +238 -0
  286. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  287. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  288. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  289. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  290. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  291. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  292. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  293. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  294. package/src/lib/floorplan/schedules.test.ts +189 -0
  295. package/src/lib/floorplan/schedules.ts +339 -0
  296. package/src/lib/floorplan/sheet-export.test.ts +100 -0
  297. package/src/lib/floorplan/sheet-export.ts +318 -0
  298. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
  299. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  300. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  301. package/src/lib/floorplan/site-plan/coverage.ts +232 -0
  302. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  303. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  304. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  305. package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
  306. package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
  307. package/src/lib/floorplan/site-plan/geometry.ts +557 -0
  308. package/src/lib/floorplan/site-plan/index.ts +71 -0
  309. package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
  310. package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
  311. package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
  312. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  313. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  314. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  315. package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
  316. package/src/lib/floorplan/types.ts +1 -27
  317. package/src/lib/fresh-planar-placement.test.ts +36 -0
  318. package/src/lib/fresh-planar-placement.ts +191 -36
  319. package/src/lib/geometry-script/author.ts +152 -0
  320. package/src/lib/geometry-script/client.ts +152 -0
  321. package/src/lib/geometry-script/protocol.ts +12 -0
  322. package/src/lib/gesture-lifecycle.test.ts +595 -0
  323. package/src/lib/gesture-lifecycle.ts +290 -0
  324. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  325. package/src/lib/glb-export.test.ts +544 -16
  326. package/src/lib/glb-export.ts +256 -125
  327. package/src/lib/grid-frame.test.ts +19 -0
  328. package/src/lib/grid-frame.ts +18 -0
  329. package/src/lib/history.test.ts +6 -0
  330. package/src/lib/history.ts +7 -0
  331. package/src/lib/hud-title.ts +109 -0
  332. package/src/lib/hydration-gated-storage.ts +49 -0
  333. package/src/lib/ifc-export.test.ts +174 -0
  334. package/src/lib/ifc-export.ts +201 -0
  335. package/src/lib/ifc-parts.ts +15 -0
  336. package/src/lib/inspector-expanded.test.ts +44 -0
  337. package/src/lib/inspector-expanded.ts +36 -0
  338. package/src/lib/interaction/scope.ts +27 -1
  339. package/src/lib/keyboard-pan.test.ts +184 -0
  340. package/src/lib/keyboard-pan.ts +238 -0
  341. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  342. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  343. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  344. package/src/lib/lot/clean-ring.test.ts +186 -0
  345. package/src/lib/lot/clean-ring.ts +217 -0
  346. package/src/lib/lot/dossier.test.ts +202 -0
  347. package/src/lib/lot/dossier.ts +498 -0
  348. package/src/lib/lot/drop-in.test.ts +326 -0
  349. package/src/lib/lot/drop-in.ts +407 -0
  350. package/src/lib/lot/index.ts +43 -0
  351. package/src/lib/lot/lot-patch.test.ts +340 -0
  352. package/src/lib/lot/lot-patch.ts +394 -0
  353. package/src/lib/lot/parcel-provider.ts +43 -0
  354. package/src/lib/lot/terrain.test.ts +161 -0
  355. package/src/lib/lot/terrain.ts +266 -0
  356. package/src/lib/material-catalog-model.ts +74 -0
  357. package/src/lib/material-paint-panel-model.ts +80 -0
  358. package/src/lib/material-paint.ts +4 -0
  359. package/src/lib/mezzanine-actions.test.ts +342 -0
  360. package/src/lib/mezzanine-draft.test.ts +501 -0
  361. package/src/lib/mezzanine-draft.ts +278 -0
  362. package/src/lib/mezzanine-messages.ts +26 -0
  363. package/src/lib/mezzanine-selection.test.ts +207 -0
  364. package/src/lib/mezzanine-stairs.ts +46 -0
  365. package/src/lib/model-export.ts +3 -1
  366. package/src/lib/node-action-movement.ts +17 -6
  367. package/src/lib/node-display-label.ts +37 -0
  368. package/src/lib/orbit-floor-clearance.test.ts +165 -0
  369. package/src/lib/orbit-floor-clearance.ts +106 -0
  370. package/src/lib/overlay-transform.test.ts +28 -0
  371. package/src/lib/overlay-transform.ts +12 -0
  372. package/src/lib/paint-commit-route.test.ts +37 -0
  373. package/src/lib/paint-commit-route.ts +17 -0
  374. package/src/lib/paint-eyedropper.test.tsx +165 -0
  375. package/src/lib/paint-eyedropper.ts +99 -0
  376. package/src/lib/paint-pass-through.test.ts +111 -0
  377. package/src/lib/paint-pass-through.ts +76 -0
  378. package/src/lib/paint-region-hover.ts +25 -0
  379. package/src/lib/paint-region-mode.ts +154 -0
  380. package/src/lib/paint-regions.test.ts +166 -0
  381. package/src/lib/paint-regions.ts +192 -0
  382. package/src/lib/paint-scope.test.ts +317 -35
  383. package/src/lib/paint-scope.ts +266 -25
  384. package/src/lib/panel-rows.ts +96 -0
  385. package/src/lib/panel-tool-options.test.ts +34 -0
  386. package/src/lib/panel-tool-options.ts +46 -0
  387. package/src/lib/parametric-node-update.ts +37 -0
  388. package/src/lib/plate-paint-affected.test.ts +300 -0
  389. package/src/lib/plate-paint-affected.ts +177 -0
  390. package/src/lib/plugin-chat.ts +50 -0
  391. package/src/lib/plugin-panels.test.ts +25 -0
  392. package/src/lib/plugin-panels.ts +94 -0
  393. package/src/lib/print-shell-compiler.ts +1 -0
  394. package/src/lib/registered-draft-snap.ts +12 -0
  395. package/src/lib/roof-duplication.ts +10 -2
  396. package/src/lib/room-assembly-overlay.test.ts +275 -0
  397. package/src/lib/room-assembly-overlay.ts +266 -0
  398. package/src/lib/room-built-on.test.ts +379 -0
  399. package/src/lib/room-built-on.ts +138 -0
  400. package/src/lib/room-construction-commands.ts +712 -0
  401. package/src/lib/room-construction.test.ts +746 -0
  402. package/src/lib/room-construction.ts +385 -0
  403. package/src/lib/room-controls-phase6.test.ts +630 -0
  404. package/src/lib/room-divide-session.ts +523 -0
  405. package/src/lib/room-handle-drag.ts +1068 -0
  406. package/src/lib/room-name-catalog.test.ts +90 -0
  407. package/src/lib/room-name-catalog.ts +168 -0
  408. package/src/lib/room-push-dimensions.test.ts +311 -0
  409. package/src/lib/room-push-dimensions.ts +251 -0
  410. package/src/lib/room-selection-commands.ts +62 -0
  411. package/src/lib/room-selection.test.ts +973 -0
  412. package/src/lib/room-selection.ts +417 -0
  413. package/src/lib/room-structure-commands.ts +94 -0
  414. package/src/lib/room-structure.test.ts +921 -0
  415. package/src/lib/room-transform-session.ts +570 -0
  416. package/src/lib/room-zone-routing.test.ts +294 -0
  417. package/src/lib/room-zone-routing.ts +197 -0
  418. package/src/lib/scene-clipboard.test.ts +364 -0
  419. package/src/lib/scene-clipboard.ts +229 -48
  420. package/src/lib/scene-material-refs.test.ts +79 -0
  421. package/src/lib/scene-material-refs.ts +79 -0
  422. package/src/lib/scene.test.ts +87 -0
  423. package/src/lib/scene.ts +39 -45
  424. package/src/lib/selection-routing.test.ts +129 -45
  425. package/src/lib/selection-routing.ts +53 -74
  426. package/src/lib/session-writes.test.ts +120 -0
  427. package/src/lib/session-writes.ts +205 -0
  428. package/src/lib/sidebar-panel.ts +62 -0
  429. package/src/lib/site-boundary.test.ts +4 -6
  430. package/src/lib/site-boundary.ts +1 -1
  431. package/src/lib/snapping-mode.test.ts +17 -2
  432. package/src/lib/snapping-mode.ts +19 -7
  433. package/src/lib/spatial-drag-plane.test.ts +53 -0
  434. package/src/lib/spatial-drag-plane.ts +19 -0
  435. package/src/lib/spatial-pointer-input.test.ts +72 -0
  436. package/src/lib/spatial-pointer-input.ts +62 -0
  437. package/src/lib/spatial-snap.test.ts +47 -0
  438. package/src/lib/terrace-draft.test.ts +145 -0
  439. package/src/lib/terrace-draft.ts +213 -0
  440. package/src/lib/terrain-panel-model.ts +117 -0
  441. package/src/lib/tool-cursor-icon.test.ts +16 -0
  442. package/src/lib/tool-cursor-icon.ts +11 -0
  443. package/src/lib/typing-target.ts +9 -0
  444. package/src/lib/units.ts +77 -39
  445. package/src/lib/vector-edges.test.ts +58 -0
  446. package/src/lib/vector-edges.ts +400 -0
  447. package/src/lib/viewer-selection.test.ts +577 -0
  448. package/src/lib/viewer-selection.ts +569 -0
  449. package/src/lib/wall-draw-variant.test.ts +71 -0
  450. package/src/lib/wall-draw-variant.ts +91 -0
  451. package/src/lib/wall-paint-region-session.ts +246 -0
  452. package/src/lib/wall-paint-region.test.ts +373 -0
  453. package/src/lib/wall-polygon-draft.test.ts +262 -0
  454. package/src/lib/wall-polygon-draft.ts +286 -0
  455. package/src/lib/wall-push.test.ts +453 -0
  456. package/src/lib/wall-region-face.ts +352 -0
  457. package/src/lib/wall-region-handles.test.ts +304 -0
  458. package/src/lib/wall-region-handles.ts +368 -0
  459. package/src/lib/wall-region-list.test.ts +119 -0
  460. package/src/lib/wall-region-list.ts +136 -0
  461. package/src/lib/wall-region-snap.ts +110 -0
  462. package/src/lib/window-interaction.ts +14 -0
  463. package/src/lib/world-grid-snap.test.ts +48 -0
  464. package/src/lib/world-grid-snap.ts +9 -5
  465. package/src/lib/zone-content.ts +4 -1
  466. package/src/lib/zone-name.test.ts +17 -0
  467. package/src/lib/zone-name.ts +17 -0
  468. package/src/store/opens-in-select-mode.test.ts +223 -0
  469. package/src/store/paint-mode.test.ts +188 -0
  470. package/src/store/rotation-snapping.test.ts +53 -0
  471. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  472. package/src/store/tool-mode.test.ts +137 -6
  473. package/src/store/use-delete-confirmation.ts +28 -1
  474. package/src/store/use-drawing-view.test.ts +24 -3
  475. package/src/store/use-drawing-view.ts +29 -1
  476. package/src/store/use-editor.tsx +442 -323
  477. package/src/store/use-floorplan-draft-preview.ts +23 -1
  478. package/src/store/use-floorplan-marquee.ts +12 -2
  479. package/src/store/use-interaction-scope.ts +155 -6
  480. package/src/store/use-quick-measurement-hud.ts +3 -3
  481. package/src/store/use-stair-build-preview.ts +13 -5
  482. package/src/store/use-wall-move-ghosts.ts +4 -2
  483. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  484. package/src/store/use-wall-region-handles-selection.ts +139 -0
  485. package/src/store/view-layout.test.ts +175 -0
  486. package/src/store/view-layout.ts +196 -0
  487. package/src/test-utils/immediate-animation-frames.ts +16 -0
  488. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  489. package/src/test-utils/selection-harness.ts +82 -0
  490. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  491. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  492. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  493. package/src/components/tools/fence/fence-drafting.ts +0 -266
  494. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  495. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  496. package/src/lib/floorplan/stairs.ts +0 -489
  497. package/src/lib/level-duplication.test.ts +0 -144
  498. 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
+ }
@@ -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
@@ -20,6 +20,10 @@ const MOUSE_SHORTCUTS = {
20
20
  icon: 'ph:mouse-right-click-fill',
21
21
  label: 'Right click',
22
22
  },
23
+ Scroll: {
24
+ icon: 'ph:mouse-scroll-fill',
25
+ label: 'Scroll',
26
+ },
23
27
  } as const
24
28
 
25
29
  // The platform-agnostic command modifier. Both Cmd and Ctrl bind the action; we
@@ -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'
@@ -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,145 @@
1
+ 'use client'
2
+
3
+ import { createContext, type ReactNode, useContext, useEffect, useRef } from 'react'
4
+ import { useIsMobile } from '../../hooks/use-mobile'
5
+
6
+ const RightStackContext = createContext(false)
7
+
8
+ /** Whether a panel renders inside the shared right column (in flow) rather than on its own. */
9
+ export function useInRightStack() {
10
+ return useContext(RightStackContext)
11
+ }
12
+
13
+ /** The inspector never shrinks below this (or its own height, if shorter) for the card, px. */
14
+ const INSPECTOR_MIN_HEIGHT = 180
15
+
16
+ /** Gap between the inspector and the card, px (`gap-2`). */
17
+ const GAP = 8
18
+
19
+ /**
20
+ * The heights neither panel shrinks below. The inspector keeps 180 px (or its
21
+ * own height, if shorter) while the full card fits under it, and never less
22
+ * than its header row. The card shows in full whenever the column allows; on a
23
+ * very short window it gives way and scrolls on itself — both stay usable.
24
+ */
25
+ export function rightStackFloors(args: {
26
+ naturalHeight: number
27
+ headerHeight: number
28
+ cardHeight: number
29
+ stackHeight: number
30
+ }): { inspector: number; card: number } {
31
+ const { naturalHeight, headerHeight, cardHeight, stackHeight } = args
32
+ const gap = naturalHeight > 0 && cardHeight > 0 ? GAP : 0
33
+ const header = Math.min(headerHeight, naturalHeight)
34
+ const inspector = Math.max(
35
+ header,
36
+ Math.min(INSPECTOR_MIN_HEIGHT, naturalHeight, stackHeight - cardHeight - gap),
37
+ )
38
+ const card = Math.max(0, Math.min(cardHeight, stackHeight - gap - inspector))
39
+ return { inspector, card }
40
+ }
41
+
42
+ /** The inspector's full height: its header plus everything its scroller holds. */
43
+ function inspectorHeights(slot: HTMLElement): { natural: number; header: number } {
44
+ const panel = slot.querySelector<HTMLElement>('[data-panel-wrapper]')
45
+ if (!panel) return { natural: 0, header: 0 }
46
+ const header = panel.querySelector<HTMLElement>('[data-panel-header]')?.offsetHeight ?? 0
47
+ const scroller = panel.querySelector<HTMLElement>('[data-panel-scroll]')
48
+ if (!scroller) return { natural: panel.offsetHeight, header }
49
+ return { natural: panel.offsetHeight - scroller.clientHeight + scroller.scrollHeight, header }
50
+ }
51
+
52
+ /**
53
+ * The right-hand column: the shortcuts card shows in full at the bottom, and
54
+ * the node inspector takes the space above it, growing down from the top and
55
+ * scrolling on itself. On a very short window the inspector keeps its header
56
+ * and the card scrolls instead. Neither hides the other. On a phone the two
57
+ * keep their own layouts (sheet / no card).
58
+ */
59
+ export function RightStack({ inspector, helper }: { inspector: ReactNode; helper: ReactNode }) {
60
+ const isMobile = useIsMobile()
61
+ const stackRef = useRef<HTMLDivElement>(null)
62
+ const inspectorRef = useRef<HTMLDivElement>(null)
63
+ const cardRef = useRef<HTMLDivElement>(null)
64
+
65
+ // `min(180px, its own height)` has no CSS form for a flex item that scrolls,
66
+ // so both floors are measured (`rightStackFloors`).
67
+ useEffect(() => {
68
+ if (isMobile) return
69
+ const stack = stackRef.current
70
+ const slot = inspectorRef.current
71
+ const card = cardRef.current
72
+ if (!(stack && slot && card)) return
73
+ let frame = 0
74
+ const fit = () => {
75
+ frame = 0
76
+ const { natural, header } = inspectorHeights(slot)
77
+ const floors = rightStackFloors({
78
+ naturalHeight: natural,
79
+ headerHeight: header,
80
+ cardHeight: card.scrollHeight,
81
+ stackHeight: stack.clientHeight,
82
+ })
83
+ const inspectorMin = `${floors.inspector}px`
84
+ const cardMin = `${floors.card}px`
85
+ if (slot.style.minHeight !== inspectorMin) slot.style.minHeight = inspectorMin
86
+ if (card.style.minHeight !== cardMin) card.style.minHeight = cardMin
87
+ // The card is click-through; it takes the pointer only when it has to scroll.
88
+ const pointer = floors.card < card.scrollHeight ? 'auto' : ''
89
+ if (card.style.pointerEvents !== pointer) card.style.pointerEvents = pointer
90
+ }
91
+ const schedule = () => {
92
+ if (!frame) frame = requestAnimationFrame(fit)
93
+ }
94
+ const resize = new ResizeObserver(schedule)
95
+ resize.observe(stack)
96
+ resize.observe(card)
97
+ // Content growing inside the scroller changes no outer size, hence the mutation watch.
98
+ // The same for the card: a new tool swaps its rows without resizing the slot.
99
+ const mutation = new MutationObserver(schedule)
100
+ for (const target of [slot, card])
101
+ mutation.observe(target, {
102
+ subtree: true,
103
+ childList: true,
104
+ attributes: true,
105
+ attributeFilter: ['class', 'hidden', 'aria-expanded'],
106
+ characterData: true,
107
+ })
108
+ fit()
109
+ return () => {
110
+ if (frame) cancelAnimationFrame(frame)
111
+ resize.disconnect()
112
+ mutation.disconnect()
113
+ }
114
+ }, [isMobile])
115
+
116
+ if (isMobile) {
117
+ return (
118
+ <>
119
+ <div className="pointer-events-auto">{inspector}</div>
120
+ <div className="pointer-events-auto">{helper}</div>
121
+ </>
122
+ )
123
+ }
124
+
125
+ return (
126
+ <RightStackContext.Provider value={true}>
127
+ <div
128
+ className="pointer-events-none fixed top-20 right-4 bottom-[74px] z-40 flex flex-col items-end gap-2"
129
+ data-right-stack
130
+ ref={stackRef}
131
+ >
132
+ <div className="flex min-h-0 flex-[0_1_auto] flex-col items-end" ref={inspectorRef}>
133
+ {inspector}
134
+ </div>
135
+ <div
136
+ className="no-scrollbar mt-auto flex min-h-0 flex-[0_1_auto] flex-col overflow-y-auto rounded-xl"
137
+ data-right-stack-card
138
+ ref={cardRef}
139
+ >
140
+ {helper}
141
+ </div>
142
+ </div>
143
+ </RightStackContext.Provider>
144
+ )
145
+ }
@@ -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 {
@@ -28,12 +28,12 @@ export function AudioSettingsDialog() {
28
28
  <DialogTrigger asChild>
29
29
  <Button className="w-full justify-start gap-2" variant="outline">
30
30
  {muted ? <VolumeX className="size-4" /> : <Volume2 className="size-4" />}
31
- Audio Settings
31
+ Audio settings
32
32
  </Button>
33
33
  </DialogTrigger>
34
34
  <DialogContent className="sm:max-w-[425px]">
35
35
  <DialogHeader>
36
- <DialogTitle>Audio Settings</DialogTitle>
36
+ <DialogTitle>Audio settings</DialogTitle>
37
37
  <DialogDescription>Adjust volume levels and mute settings</DialogDescription>
38
38
  </DialogHeader>
39
39
  <div className="space-y-6 py-4">