@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
@@ -3,25 +3,47 @@ import { Pencil } from 'lucide-react'
3
3
  import { memo, useCallback, useEffect, useRef, useState } from 'react'
4
4
  import { cn } from './../../../../../lib/utils'
5
5
 
6
- interface InlineRenameInputProps {
7
- nodeId: AnyNodeId
6
+ interface InlineRenameFieldProps {
7
+ name: string | undefined
8
+ /** Receives the trimmed name, or `undefined` when cleared. */
9
+ onRename: (name: string | undefined) => void
8
10
  isEditing: boolean
9
11
  onStopEditing: () => void
10
12
  defaultName: string
13
+ /** Shown instead of the stored name when not editing (a level's display name). */
14
+ displayName?: string
11
15
  className?: string
12
16
  onStartEditing?: () => void
13
17
  }
14
18
 
19
+ type InlineRenameInputProps = Omit<InlineRenameFieldProps, 'name' | 'onRename'> & {
20
+ nodeId: AnyNodeId
21
+ }
22
+
23
+ /** Renames a scene node through `updateNode`. */
15
24
  export const InlineRenameInput = memo(function InlineRenameInput({
16
25
  nodeId,
26
+ ...props
27
+ }: InlineRenameInputProps) {
28
+ const updateNode = useScene((s) => s.updateNode)
29
+ const name = useScene((s) => s.nodes[nodeId]?.name)
30
+ const handleRename = useCallback(
31
+ (next: string | undefined) => updateNode(nodeId, { name: next }),
32
+ [nodeId, updateNode],
33
+ )
34
+ return <InlineRenameField {...props} name={name} onRename={handleRename} />
35
+ })
36
+
37
+ export const InlineRenameField = memo(function InlineRenameField({
38
+ name,
39
+ onRename,
17
40
  isEditing,
18
41
  onStopEditing,
19
42
  defaultName,
43
+ displayName,
20
44
  className,
21
45
  onStartEditing,
22
- }: InlineRenameInputProps) {
23
- const updateNode = useScene((s) => s.updateNode)
24
- const name = useScene((s) => s.nodes[nodeId]?.name)
46
+ }: InlineRenameFieldProps) {
25
47
  const [value, setValue] = useState(name || '')
26
48
  const inputRef = useRef<HTMLInputElement>(null)
27
49
  const inputSize = Math.max((value || defaultName).length, 1)
@@ -42,10 +64,10 @@ export const InlineRenameInput = memo(function InlineRenameInput({
42
64
  const handleSave = useCallback(() => {
43
65
  const trimmed = value.trim()
44
66
  if (trimmed !== name) {
45
- updateNode(nodeId, { name: trimmed || undefined })
67
+ onRename(trimmed || undefined)
46
68
  }
47
69
  onStopEditing()
48
- }, [value, nodeId, name, updateNode, onStopEditing])
70
+ }, [value, name, onRename, onStopEditing])
49
71
 
50
72
  const handleKeyDown = (e: React.KeyboardEvent) => {
51
73
  if (e.key === 'Enter') {
@@ -61,7 +83,7 @@ export const InlineRenameInput = memo(function InlineRenameInput({
61
83
  return (
62
84
  <div className="group/rename flex h-5 min-w-0 items-center gap-1">
63
85
  <span className={cn('truncate border-transparent border-b', className)}>
64
- {name || defaultName}
86
+ {displayName ?? (name || defaultName)}
65
87
  </span>
66
88
  {onStartEditing && (
67
89
  <button
@@ -8,7 +8,6 @@ import { InlineRenameInput } from './inline-rename-input'
8
8
  import {
9
9
  focusTreeNode,
10
10
  handleTreeSelection,
11
- routeTreeSelectionToNode,
12
11
  TreeNode,
13
12
  TreeNodeWrapper,
14
13
  } from './tree-node'
@@ -71,9 +70,8 @@ export const ItemTreeNode = memo(function ItemTreeNode({
71
70
  (e: React.MouseEvent) => {
72
71
  e.stopPropagation()
73
72
  handleTreeSelection(e, nodeId, useViewer.getState().selection.selectedIds, setSelection)
74
- routeTreeSelectionToNode(node)
75
73
  },
76
- [node, nodeId, setSelection],
74
+ [nodeId, setSelection],
77
75
  )
78
76
 
79
77
  const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
@@ -3,7 +3,7 @@ import { markPerfAction, useViewer } from '@pascal-app/viewer'
3
3
  import { Layers } from 'lucide-react'
4
4
  import { memo, useCallback, useState } from 'react'
5
5
  import { useShallow } from 'zustand/react/shallow'
6
- import { getDefaultLevelName } from '@pascal-app/core'
6
+ import { getDefaultLevelName, getLevelDisplayName } from '@pascal-app/core'
7
7
  import { InlineRenameInput } from './inline-rename-input'
8
8
  import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
9
9
  import { TreeNodeActions } from './tree-node-actions'
@@ -26,6 +26,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
26
26
  useShallow((s) => (s.nodes[nodeId] as LevelNode | undefined)?.children ?? []),
27
27
  )
28
28
  const level = useScene((s) => (s.nodes[nodeId] as LevelNode | undefined)?.level ?? 0)
29
+ const levelName = useScene((s) => (s.nodes[nodeId] as LevelNode | undefined)?.name)
29
30
  const isSelected = useViewer((state) => state.selection.levelId === nodeId)
30
31
  const isHovered = useViewer((state) => state.hoveredId === nodeId)
31
32
  const setSelection = useViewer((state) => state.setSelection)
@@ -40,6 +41,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
40
41
  const handleStopEditing = useCallback(() => setIsEditing(false), [])
41
42
 
42
43
  const defaultName = getDefaultLevelName(level)
44
+ const displayName = getLevelDisplayName({ name: levelName, level })
43
45
 
44
46
  return (
45
47
  <TreeNodeWrapper
@@ -55,6 +57,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
55
57
  label={
56
58
  <InlineRenameInput
57
59
  defaultName={defaultName}
60
+ displayName={displayName}
58
61
  isEditing={isEditing}
59
62
  nodeId={nodeId}
60
63
  onStartEditing={handleStartEditing}
@@ -10,7 +10,6 @@ import { InlineRenameInput } from './inline-rename-input'
10
10
  import {
11
11
  focusTreeNode,
12
12
  handleTreeSelection,
13
- routeTreeSelectionToNode,
14
13
  TreeNode,
15
14
  TreeNodeWrapper,
16
15
  } from './tree-node'
@@ -99,9 +98,8 @@ export const RegistryTreeNode = memo(function RegistryTreeNode({
99
98
  useViewer.getState().selection.selectedIds,
100
99
  setSelection,
101
100
  )
102
- routeTreeSelectionToNode(node)
103
101
  },
104
- [node, nodeId, setSelection],
102
+ [nodeId, setSelection],
105
103
  )
106
104
 
107
105
  return (
@@ -49,15 +49,12 @@ export const RoofTreeNode = memo(function RoofTreeNode({
49
49
  const handleClick = useCallback(
50
50
  (e: React.MouseEvent) => {
51
51
  e.stopPropagation()
52
- const handled = handleTreeSelection(
52
+ handleTreeSelection(
53
53
  e,
54
54
  nodeId,
55
55
  useViewer.getState().selection.selectedIds,
56
56
  setSelection,
57
57
  )
58
- if (!handled && useEditor.getState().phase === 'furnish') {
59
- useEditor.getState().setPhase('structure')
60
- }
61
58
  if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
62
59
  setRoofHostDragArmedId(nodeId)
63
60
  }
@@ -9,7 +9,6 @@ import { InlineRenameInput } from './inline-rename-input'
9
9
  import {
10
10
  focusTreeNode,
11
11
  handleTreeSelection,
12
- routeTreeSelectionToNode,
13
12
  TreeNode,
14
13
  TreeNodeWrapper,
15
14
  } from './tree-node'
@@ -70,9 +69,8 @@ export const ShelfTreeNode = memo(function ShelfTreeNode({
70
69
  (e: React.MouseEvent) => {
71
70
  e.stopPropagation()
72
71
  handleTreeSelection(e, nodeId, useViewer.getState().selection.selectedIds, setSelection)
73
- routeTreeSelectionToNode(node)
74
72
  },
75
- [node, nodeId, setSelection],
73
+ [nodeId, setSelection],
76
74
  )
77
75
 
78
76
  const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
@@ -1,9 +1,8 @@
1
- import { type AnyNodeId, type SlabNode, useScene } from '@pascal-app/core'
1
+ import { type AnyNodeId, floorFootprintName, type SlabNode, useScene } from '@pascal-app/core'
2
2
  import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useState } from 'react'
5
5
  import { formatAreaLabel } from './../../../../../lib/measurements'
6
- import useEditor from './../../../../../store/use-editor'
7
6
  import { InlineRenameInput } from './inline-rename-input'
8
7
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
9
8
  import { TreeNodeActions } from './tree-node-actions'
@@ -27,19 +26,22 @@ export const SlabTreeNode = memo(function SlabTreeNode({
27
26
  const setSelection = useViewer((state) => state.setSelection)
28
27
  const setHoveredId = useViewer((state) => state.setHoveredId)
29
28
  const unit = useViewer((state) => state.unit)
29
+ // A base plate is one footprint's "Floor & foundation": listed by the
30
+ // footprint's name ("Shared floor", "Lanai floor"), opening that panel.
31
+ const footprint = useScene((s) => {
32
+ const node = s.nodes[nodeId]
33
+ return node?.type === 'slab' && node.plateRole === 'base' ? floorFootprintName(s.nodes, node) : null
34
+ })
30
35
 
31
36
  const handleClick = useCallback(
32
37
  (e: React.MouseEvent) => {
33
38
  e.stopPropagation()
34
- const handled = handleTreeSelection(
39
+ handleTreeSelection(
35
40
  e,
36
41
  nodeId,
37
42
  useViewer.getState().selection.selectedIds,
38
43
  setSelection,
39
44
  )
40
- if (!handled && useEditor.getState().phase === 'furnish') {
41
- useEditor.getState().setPhase('structure')
42
- }
43
45
  },
44
46
  [nodeId, setSelection],
45
47
  )
@@ -63,13 +65,19 @@ export const SlabTreeNode = memo(function SlabTreeNode({
63
65
  isSelected={isSelected}
64
66
  isVisible={isVisible}
65
67
  label={
66
- <InlineRenameInput
67
- defaultName={defaultName}
68
- isEditing={isEditing}
69
- nodeId={nodeId}
70
- onStartEditing={handleStartEditing}
71
- onStopEditing={handleStopEditing}
72
- />
68
+ footprint ? (
69
+ <span className="truncate" data-footprint-entry={nodeId}>
70
+ {footprint}
71
+ </span>
72
+ ) : (
73
+ <InlineRenameInput
74
+ defaultName={defaultName}
75
+ isEditing={isEditing}
76
+ nodeId={nodeId}
77
+ onStartEditing={handleStartEditing}
78
+ onStopEditing={handleStopEditing}
79
+ />
80
+ )
73
81
  }
74
82
  nodeId={nodeId}
75
83
  onClick={handleClick}
@@ -3,7 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useState } from 'react'
5
5
  import { SnapTargetIcon } from '../../../snap-target-badge'
6
- import useEditor from './../../../../../store/use-editor'
7
6
  import { InlineRenameInput } from './inline-rename-input'
8
7
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
9
8
  import { TreeNodeActions } from './tree-node-actions'
@@ -30,15 +29,12 @@ export const SolarPanelTreeNode = memo(function SolarPanelTreeNode({
30
29
  const handleClick = useCallback(
31
30
  (e: React.MouseEvent) => {
32
31
  e.stopPropagation()
33
- const handled = handleTreeSelection(
32
+ handleTreeSelection(
34
33
  e,
35
34
  nodeId,
36
35
  useViewer.getState().selection.selectedIds,
37
36
  setSelection,
38
37
  )
39
- if (!handled && useEditor.getState().phase === 'furnish') {
40
- useEditor.getState().setPhase('structure')
41
- }
42
38
  },
43
39
  [nodeId, setSelection],
44
40
  )
@@ -4,7 +4,6 @@ import { type SpawnNode, useScene } from '@pascal-app/core'
4
4
  import { useViewer } from '@pascal-app/viewer'
5
5
  import Image from 'next/image'
6
6
  import { memo, useCallback, useState } from 'react'
7
- import useEditor from './../../../../../store/use-editor'
8
7
  import { InlineRenameInput } from './inline-rename-input'
9
8
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
10
9
  import { TreeNodeActions } from './tree-node-actions'
@@ -30,15 +29,12 @@ export const SpawnTreeNode = memo(function SpawnTreeNode({
30
29
  const handleClick = useCallback(
31
30
  (e: React.MouseEvent) => {
32
31
  e.stopPropagation()
33
- const handled = handleTreeSelection(
32
+ handleTreeSelection(
34
33
  e,
35
34
  nodeId,
36
35
  useViewer.getState().selection.selectedIds,
37
36
  setSelection,
38
37
  )
39
- if (!handled && useEditor.getState().phase === 'furnish') {
40
- useEditor.getState().setPhase('structure')
41
- }
42
38
  },
43
39
  [nodeId, setSelection],
44
40
  )
@@ -4,7 +4,6 @@ import { AnimatePresence } from 'motion/react'
4
4
  import Image from 'next/image'
5
5
  import { memo, useCallback, useEffect, useState } from 'react'
6
6
  import { useShallow } from 'zustand/react/shallow'
7
- import useEditor from '../../../../../store/use-editor'
8
7
  import { InlineRenameInput } from './inline-rename-input'
9
8
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
10
9
  import { TreeNodeActions } from './tree-node-actions'
@@ -48,15 +47,12 @@ export const StairTreeNode = memo(function StairTreeNode({
48
47
  const handleClick = useCallback(
49
48
  (e: React.MouseEvent) => {
50
49
  e.stopPropagation()
51
- const handled = handleTreeSelection(
50
+ handleTreeSelection(
52
51
  e,
53
52
  nodeId,
54
53
  useViewer.getState().selection.selectedIds,
55
54
  setSelection,
56
55
  )
57
- if (!handled && useEditor.getState().phase === 'furnish') {
58
- useEditor.getState().setPhase('structure')
59
- }
60
56
  },
61
57
  [nodeId, setSelection],
62
58
  )
@@ -3,8 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import { ChevronRight } from 'lucide-react'
4
4
  import { AnimatePresence, motion } from 'motion/react'
5
5
  import { forwardRef, memo, useEffect, useRef } from 'react'
6
- import { resolveNodeSelectionTarget } from '../../../../../lib/selection-routing'
7
- import useEditor from '../../../../../store/use-editor'
8
6
  import { expandSessionSelectionForNode } from '../../../../../store/use-session-groups'
9
7
 
10
8
  export function handleTreeSelection(
@@ -63,31 +61,6 @@ export function focusTreeNode(nodeId: AnyNodeId) {
63
61
  emitter.emit('camera-controls:focus', { nodeId })
64
62
  }
65
63
 
66
- export function routeTreeSelectionToNode(node: AnyNode | null | undefined) {
67
- const target = node ? resolveNodeSelectionTarget(node) : null
68
- if (!target) return
69
-
70
- const selectedIdsAfterClick = useViewer.getState().selection.selectedIds
71
- const editor = useEditor.getState()
72
- let didRoute = false
73
-
74
- if (target.phase !== editor.phase) {
75
- editor.setPhase(target.phase)
76
- didRoute = true
77
- }
78
- if (
79
- target.phase === 'structure' &&
80
- target.structureLayer &&
81
- target.structureLayer !== useEditor.getState().structureLayer
82
- ) {
83
- useEditor.getState().setStructureLayer(target.structureLayer)
84
- didRoute = true
85
- }
86
- if (didRoute) {
87
- useViewer.getState().setSelection({ selectedIds: selectedIdsAfterClick })
88
- }
89
- }
90
-
91
64
  import { cn } from '../../../../../lib/utils'
92
65
  import { BuildingTreeNode } from './building-tree-node'
93
66
  import { CeilingTreeNode } from './ceiling-tree-node'
@@ -310,14 +283,15 @@ export const TreeNodeWrapper = forwardRef<HTMLDivElement, TreeNodeWrapperProps>(
310
283
  />
311
284
  )}
312
285
 
313
- <button
314
- className="z-10 flex h-4 w-4 shrink-0 items-center justify-center bg-inherit"
315
- onClick={(e) => {
316
- e.stopPropagation()
317
- onToggle()
318
- }}
319
- >
320
- {hasChildren ? (
286
+ {/* A row with nothing to fold keeps the space, and a click there selects the row. */}
287
+ {hasChildren ? (
288
+ <button
289
+ className="z-10 flex h-4 w-4 shrink-0 items-center justify-center bg-inherit"
290
+ onClick={(e) => {
291
+ e.stopPropagation()
292
+ onToggle()
293
+ }}
294
+ >
321
295
  <motion.div
322
296
  animate={{ rotate: expanded ? 90 : 0 }}
323
297
  initial={false}
@@ -325,8 +299,10 @@ export const TreeNodeWrapper = forwardRef<HTMLDivElement, TreeNodeWrapperProps>(
325
299
  >
326
300
  <ChevronRight className="h-3 w-3" />
327
301
  </motion.div>
328
- ) : null}
329
- </button>
302
+ </button>
303
+ ) : (
304
+ <span className="h-4 w-4 shrink-0" />
305
+ )}
330
306
  <div className="flex min-w-0 flex-1 items-center gap-2">
331
307
  <span
332
308
  className={cn(
@@ -14,7 +14,7 @@ import { useShallow } from 'zustand/react/shallow'
14
14
  import { ColorDot } from './../../../../../components/ui/primitives/color-dot'
15
15
  import { assignZoneToUnit, enterUnitFocus } from './../../../../../lib/units'
16
16
  import { InlineRenameInput } from './inline-rename-input'
17
- import { routeTreeSelectionToNode, TreeNodeWrapper } from './tree-node'
17
+ import { TreeNodeWrapper } from './tree-node'
18
18
 
19
19
  type UnitWarningCode = ReturnType<typeof unitWarnings>[number]['code']
20
20
 
@@ -55,7 +55,6 @@ export const UnitZoneRow = memo(function UnitZoneRow({
55
55
  event.stopPropagation()
56
56
  if (!zone) return
57
57
  setSelection({ levelId: zone.parentId as LevelNode['id'], zoneId })
58
- routeTreeSelectionToNode(zone)
59
58
  },
60
59
  [zone, zoneId, setSelection],
61
60
  )
@@ -94,7 +93,7 @@ export const UnitZoneRow = memo(function UnitZoneRow({
94
93
  keepIconColor
95
94
  label={
96
95
  <span className="flex min-w-0 items-baseline gap-1.5">
97
- <span className="truncate">{zone.name || 'Zone'}</span>
96
+ <span className="truncate">{zone.name || (zone.spaceRole === 'room' ? 'Room' : 'Zone')}</span>
98
97
  {levelLabel && (
99
98
  <span className="shrink-0 text-muted-foreground text-xs">{levelLabel}</span>
100
99
  )}
@@ -3,7 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useEffect, useRef, useState } from 'react'
5
5
  import { useShallow } from 'zustand/react/shallow'
6
- import useEditor from './../../../../../store/use-editor'
7
6
  import { InlineRenameInput } from './inline-rename-input'
8
7
  import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
9
8
  import { TreeNodeActions } from './tree-node-actions'
@@ -52,15 +51,12 @@ export const WallTreeNode = memo(function WallTreeNode({
52
51
  const handleClick = useCallback(
53
52
  (e: React.MouseEvent) => {
54
53
  e.stopPropagation()
55
- const handled = handleTreeSelection(
54
+ handleTreeSelection(
56
55
  e,
57
56
  nodeId,
58
57
  useViewer.getState().selection.selectedIds,
59
58
  setSelection,
60
59
  )
61
- if (!handled && useEditor.getState().phase === 'furnish') {
62
- useEditor.getState().setPhase('structure')
63
- }
64
60
  },
65
61
  [nodeId, setSelection],
66
62
  )
@@ -5,7 +5,6 @@ import { useViewer } from '@pascal-app/viewer'
5
5
  import Image from 'next/image'
6
6
  import { memo, useCallback, useState } from 'react'
7
7
  import { resolveNodeSnapTarget, SnapTargetIcon } from '../../../snap-target-badge'
8
- import useEditor from './../../../../../store/use-editor'
9
8
  import { InlineRenameInput } from './inline-rename-input'
10
9
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
11
10
  import { TreeNodeActions } from './tree-node-actions'
@@ -32,15 +31,12 @@ export const WindowTreeNode = memo(function WindowTreeNode({
32
31
  const handleClick = useCallback(
33
32
  (e: React.MouseEvent) => {
34
33
  e.stopPropagation()
35
- const handled = handleTreeSelection(
34
+ handleTreeSelection(
36
35
  e,
37
36
  nodeId,
38
37
  useViewer.getState().selection.selectedIds,
39
38
  setSelection,
40
39
  )
41
- if (!handled && useEditor.getState().phase === 'furnish') {
42
- useEditor.getState().setPhase('structure')
43
- }
44
40
  },
45
41
  [nodeId, setSelection],
46
42
  )
@@ -3,6 +3,11 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import { memo, useCallback, useState } from 'react'
4
4
  import { ColorDot } from './../../../../../components/ui/primitives/color-dot'
5
5
  import { formatAreaLabel } from './../../../../../lib/measurements'
6
+ import {
7
+ selectZoneOrRoom,
8
+ useZoneSelected,
9
+ zoneKindLabel,
10
+ } from './../../../../../lib/room-zone-routing'
6
11
  import { toggleZoneMembership } from './../../../../../lib/units'
7
12
  import { InlineRenameInput } from './inline-rename-input'
8
13
  import { focusTreeNode, TreeNodeWrapper } from './tree-node'
@@ -25,9 +30,9 @@ export const ZoneTreeNode = memo(function ZoneTreeNode({
25
30
  const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
26
31
  const color = useScene((s) => (s.nodes[nodeId] as ZoneNode | undefined)?.color)
27
32
  const polygon = useScene((s) => (s.nodes[nodeId] as ZoneNode | undefined)?.polygon ?? [])
28
- const isSelected = useViewer((state) => state.selection.zoneId === nodeId)
33
+ const isSelected = useZoneSelected(nodeId)
34
+ const kind = useScene((s) => zoneKindLabel(s.nodes[nodeId] as ZoneNode | undefined))
29
35
  const isHovered = useViewer((state) => state.hoveredId === nodeId)
30
- const setSelection = useViewer((state) => state.setSelection)
31
36
  const setHoveredId = useViewer((state) => state.setHoveredId)
32
37
  const unit = useViewer((state) => state.unit)
33
38
  const focusedUnitId = useViewer((state) => state.focusedUnitId)
@@ -36,14 +41,14 @@ export const ZoneTreeNode = memo(function ZoneTreeNode({
36
41
  return focused?.type === 'unit' ? focused : null
37
42
  })
38
43
 
39
- const handleClick = useCallback(() => setSelection({ zoneId: nodeId }), [nodeId, setSelection])
44
+ const handleClick = useCallback(() => selectZoneOrRoom(nodeId), [nodeId])
40
45
  const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
41
46
  const handleMouseEnter = useCallback(() => setHoveredId(nodeId), [nodeId, setHoveredId])
42
47
  const handleMouseLeave = useCallback(() => setHoveredId(null), [setHoveredId])
43
48
  const handleStartEditing = useCallback(() => setIsEditing(true), [])
44
49
  const handleStopEditing = useCallback(() => setIsEditing(false), [])
45
50
 
46
- const defaultName = `Zone (${formatAreaLabel(calculatePolygonArea(polygon), unit)})`
51
+ const defaultName = `${kind} (${formatAreaLabel(calculatePolygonArea(polygon), unit)})`
47
52
 
48
53
  return (
49
54
  <TreeNodeWrapper
@@ -137,7 +137,6 @@ export function ZonePanel() {
137
137
  const currentLevelId = useViewer((state) => state.selection.levelId)
138
138
  const selectedZoneId = useViewer((state) => state.selection.zoneId)
139
139
  const setSelection = useViewer((state) => state.setSelection)
140
- const setPhase = useEditor((state) => state.setPhase)
141
140
  const setMode = useEditor((state) => state.setMode)
142
141
  const setTool = useEditor((state) => state.setTool)
143
142
 
@@ -149,7 +148,6 @@ export function ZonePanel() {
149
148
 
150
149
  const handleAddZone = () => {
151
150
  if (currentLevelId) {
152
- setPhase('structure')
153
151
  setMode('build')
154
152
  setTool('zone')
155
153
  }
@@ -197,14 +195,14 @@ export function ZonePanel() {
197
195
  />
198
196
  <ActionButton
199
197
  className="w-full flex-none"
200
- icon={<Trash2 className="h-4 w-4 text-red-400" />}
198
+ icon={<Trash2 className="h-4 w-4 text-red-600 dark:text-red-400" />}
201
199
  label="Delete"
202
200
  onClick={() => deleteSelectedZone(false)}
203
201
  type="button"
204
202
  />
205
203
  <ActionButton
206
204
  className="w-full flex-none"
207
- icon={<Trash2 className="h-4 w-4 text-red-400" />}
205
+ icon={<Trash2 className="h-4 w-4 text-red-600 dark:text-red-400" />}
208
206
  label="Delete with contents"
209
207
  onClick={() => deleteSelectedZone(true)}
210
208
  type="button"
@@ -19,6 +19,16 @@ export type SidebarTab = {
19
19
  * keeps the icon highlighted regardless of collapse.
20
20
  */
21
21
  noPanel?: boolean
22
+ /** Runs when the entry is picked — with `noPanel`, to put its surface on the stage. */
23
+ onSelect?: () => void
24
+ /**
25
+ * For a `noPanel` entry whose surface can leave the stage on its own (a view
26
+ * closed from the view bar): false once it has, and the rail goes back to
27
+ * the panel that was open before it.
28
+ */
29
+ stageActive?: boolean
30
+ /** Runs when the rail moves from this entry to another: a stage entry takes its surface down. */
31
+ onDeselect?: () => void
22
32
  }
23
33
 
24
34
  interface TabBarProps {
@@ -37,8 +47,8 @@ export function TabBar({ tabs, activeTab, onTabChange }: TabBarProps) {
37
47
  className={cn(
38
48
  'relative h-7 rounded-md px-3 font-medium text-sm transition-colors',
39
49
  isActive
40
- ? 'bg-accent text-foreground'
41
- : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground',
50
+ ? 'bg-sidebar-accent text-foreground'
51
+ : 'text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground',
42
52
  )}
43
53
  key={tab.id}
44
54
  onClick={() => {
@@ -91,8 +101,8 @@ export function IconRail({ tabs, activeTab, collapsed, onIconClick }: IconRailPr
91
101
  className={cn(
92
102
  'group flex h-11 w-11 items-center justify-center rounded-xl transition-all duration-200 [&_img]:transition-[opacity,filter] [&_img]:duration-200',
93
103
  showActive
94
- ? 'bg-accent text-foreground shadow-sm [&_img]:opacity-100 [&_img]:grayscale-0'
95
- : 'text-muted-foreground hover:bg-accent/50 hover:text-foreground [&_img]:opacity-60 [&_img]:grayscale hover:[&_img]:opacity-100 hover:[&_img]:grayscale-0',
104
+ ? 'bg-sidebar-accent text-foreground shadow-sm [&_img]:opacity-100 [&_img]:grayscale-0'
105
+ : 'text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground [&_img]:opacity-60 [&_img]:grayscale hover:[&_img]:opacity-100 hover:[&_img]:grayscale-0',
96
106
  )}
97
107
  onClick={() => {
98
108
  triggerSFX('sfx:menu-click')
@@ -12,28 +12,37 @@ import {
12
12
  useSyncExternalStore,
13
13
  } from 'react'
14
14
  import useEditor from '../../../store/use-editor'
15
+ import { isViewVisible } from '../../../store/view-layout'
16
+ import { PluginsView } from '../../editor/views/plugins-view'
17
+ import { PLUGINS_VIEW_ID } from '../../editor/views/use-editor-views'
15
18
  import {
16
19
  editorHostPanelRegistry,
17
20
  type EditorHostPanel,
18
21
  managedPluginIds,
19
22
  showsPluginManager,
20
23
  } from '../../../lib/plugin-panels'
24
+ import { IconRefImage } from '../icon-ref'
21
25
  import { ErrorBoundary } from '../primitives/error-boundary'
22
26
  import type { ExtraPanel } from './icon-rail'
23
- import { PluginsPanel } from './panels/plugins-panel'
24
27
 
28
+ // The rail's "+" puts the plugin directory on the stage, as a view in the
29
+ // focused pane, and stays lit while it is there instead of leaving an
30
+ // unrelated panel open beside it. `component` serves the v1 sidebar.
25
31
  const pluginsManagerPanel: ExtraPanel = {
26
32
  id: 'plugins',
27
33
  label: 'Plugins',
28
- icon: <Plus className="h-5 w-5" />,
29
- component: PluginsPanel,
34
+ icon: <img alt="" className="size-8 object-contain" src="/icons/plugins.webp" />,
35
+ component: PluginsView,
36
+ noPanel: true,
37
+ onSelect: () => useEditor.getState().showView(PLUGINS_VIEW_ID),
38
+ onDeselect: () => useEditor.getState().closeView(PLUGINS_VIEW_ID),
30
39
  }
31
40
 
32
41
  /** Resolve a plugin's {@link IconRef} into a rail-sized React node. Mirrors the
33
42
  * inspector's `renderIcon`, sized for the 24px icon-rail button. */
34
43
  function renderIconRef(ref: IconRef): ReactNode {
35
44
  if (ref.kind === 'url') {
36
- return <img alt="" className="h-5 w-5 object-contain" src={ref.src} />
45
+ return <IconRefImage className="h-5 w-5" src={ref.src} />
37
46
  }
38
47
  if (ref.kind === 'iconify') {
39
48
  return <Icon height={20} icon={ref.name} width={20} />
@@ -106,6 +115,7 @@ export function useHostPanels(hostPanels?: ExtraPanel[]): ExtraPanel[] {
106
115
  const workspaceMode = useEditor((s) => s.workspaceMode)
107
116
  const installedPlugins = useScene((s) => s.installedPlugins)
108
117
  const readOnly = useScene((s) => s.readOnly)
118
+ const pluginsViewShown = useEditor((s) => isViewVisible(s, PLUGINS_VIEW_ID))
109
119
  const hostIds = new Set(hostPanels?.map((p) => p.id))
110
120
 
111
121
  useEffect(() => {
@@ -141,7 +151,7 @@ export function useHostPanels(hostPanels?: ExtraPanel[]): ExtraPanel[] {
141
151
  readOnly,
142
152
  workspaceMode,
143
153
  })
144
- ? [pluginsManagerPanel]
154
+ ? [{ ...pluginsManagerPanel, stageActive: pluginsViewShown }]
145
155
  : []
146
156
  return [...(hostPanels ?? []), ...fromRegistry, ...manager]
147
157
  }