@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
@@ -22,11 +22,16 @@ import {
22
22
  type WindowNode,
23
23
  } from '@pascal-app/core'
24
24
  import { useViewer } from '@pascal-app/viewer'
25
- import { useCallback, useEffect, useState } from 'react'
25
+ import { useCallback, useEffect, useMemo, useState } from 'react'
26
+ import { useSelectedRoom, useZoneRoom } from '../../../hooks/use-selected-room'
27
+ import { FloorOpeningPanel } from './floor-opening-panel'
28
+ import { OpenRoomPanel, RoomPanel } from './room-panel'
26
29
  import { useIsMobile } from '../../../hooks/use-mobile'
27
30
  import { shouldShowEditingControls } from '../../../lib/interaction/overlay-policy'
31
+ import { captureElementActionOrigin, completeElementAction } from '../../../lib/room-zone-routing'
28
32
  import { sfxEmitter } from '../../../lib/sfx-bus'
29
33
  import useEditor from '../../../store/use-editor'
34
+ import { duplicateNodeAndPickUp } from '../../editor/duplicate-node'
30
35
  import { deleteSelection, duplicateSelectionAndPickUp, startGroupPickUp } from '../../editor/group-actions'
31
36
  import { resolveHomogeneousSelection } from './homogeneous-selection'
32
37
  import { MobilePanelSheet } from './mobile-panel-sheet'
@@ -34,7 +39,7 @@ import { MobileSelectionBar } from './mobile-selection-bar'
34
39
  import { MultiParametricInspector } from './multi-parametric-inspector'
35
40
  import { MultiSelectionPanel } from './multi-selection-panel'
36
41
  import { getNodeDisplay, getTypeDisplay } from './node-display'
37
- import { resetDesktopInspectorCollapsed } from './panel-wrapper'
42
+ import { InspectedNodesContext } from './panel-wrapper'
38
43
  import { ParametricInspector } from './parametric-inspector'
39
44
  import { ReferencePanel } from './reference-panel'
40
45
  import { formatSelectionBreakdown } from './selection-breakdown'
@@ -127,24 +132,16 @@ function MobilePanelLayer({
127
132
  }, [node, setMovingNode, clearSelection])
128
133
 
129
134
  const handleDuplicate = useCallback(() => {
130
- if (!isMovableNode(node)) return
131
- sfxEmitter.emit('sfx:item-pick')
132
- const cloned = structuredClone(node) as MovableNode & { id?: AnyNodeId }
133
- delete (cloned as { id?: AnyNodeId }).id
134
- const prevMeta =
135
- cloned.metadata && typeof cloned.metadata === 'object' && !Array.isArray(cloned.metadata)
136
- ? (cloned.metadata as Record<string, unknown>)
137
- : {}
138
- cloned.metadata = { ...prevMeta, isNew: true }
139
- setMovingNode(cloned as MovableNode)
140
- clearSelection()
141
- }, [node, setMovingNode, clearSelection])
135
+ if (isMovableNode(node)) duplicateNodeAndPickUp(node)
136
+ }, [node])
142
137
 
143
138
  const handleDelete = useCallback(() => {
144
139
  if (!node) return
140
+ const origin = captureElementActionOrigin([node.id])
145
141
  sfxEmitter.emit('sfx:item-delete')
146
142
  deleteNode(node.id)
147
143
  clearSelection()
144
+ completeElementAction(origin)
148
145
  }, [node, deleteNode, clearSelection])
149
146
 
150
147
  if (!(node || isReference)) return null
@@ -222,6 +219,7 @@ export function PanelManager({
222
219
  multiSelectionFooter?: React.ReactNode
223
220
  }) {
224
221
  const isMobile = useIsMobile()
222
+ const room = useSelectedRoom()
225
223
  const selectedIds = useViewer((s) => s.selection.selectedIds)
226
224
  const selectedZoneId = useViewer((s) => s.selection.zoneId)
227
225
  const setSelection = useViewer((s) => s.setSelection)
@@ -248,6 +246,16 @@ export function PanelManager({
248
246
  : '',
249
247
  )
250
248
 
249
+ // A room reached as a zone (a unit's member, a zone kept by a route that is not
250
+ // room-first) still gets the room panel.
251
+ const panelZoneId =
252
+ selectedIds.length === 0
253
+ ? selectedZoneId
254
+ : selectedNodeType === 'zone'
255
+ ? (selectedIds[0] ?? null)
256
+ : null
257
+ const zoneRoom = useZoneRoom(panelZoneId)
258
+
251
259
  // Node and reference selection are mutually exclusive: selecting a guide
252
260
  // clears the node selection (handleGuideSelect), but node selection never
253
261
  // cleared a lingering reference — so clicking a wall with a floorplan
@@ -255,74 +263,101 @@ export function PanelManager({
255
263
  // moment a scene selection appears.
256
264
  const setSelectedReferenceId = useEditor((s) => s.setSelectedReferenceId)
257
265
  useEffect(() => {
258
- if (selectedIds.length > 0 || selectedZoneId) {
266
+ if (selectedIds.length > 0 || selectedZoneId || room) {
259
267
  setSelectedReferenceId(null)
260
268
  }
261
- }, [selectedIds, selectedZoneId, setSelectedReferenceId])
269
+ }, [selectedIds, selectedZoneId, room, setSelectedReferenceId])
262
270
 
263
- // The inspector's expanded state is shared across panel swaps, but a fresh
264
- // selection after everything was deselected should open collapsed again.
265
- const hasAnySelection = selectedIds.length > 0 || Boolean(selectedZoneId) || Boolean(selectedReferenceId)
266
- useEffect(() => {
267
- if (!hasAnySelection) {
268
- resetDesktopInspectorCollapsed()
269
- }
270
- }, [hasAnySelection])
271
+ // What the card shows, for its Collections section: a room is its zone.
272
+ const inspectedKey =
273
+ room && selectedIds.length === 0
274
+ ? room.zoneId
275
+ : panelZoneId && zoneRoom
276
+ ? panelZoneId
277
+ : selectedReferenceId
278
+ ? ''
279
+ : selectedIds.length === 0
280
+ ? (selectedZoneId ?? '')
281
+ : selectedIds.join(',')
282
+ const inspectedIds = useMemo(
283
+ () => (inspectedKey ? (inspectedKey.split(',') as AnyNodeId[]) : []),
284
+ [inspectedKey],
285
+ )
271
286
 
272
287
  if (!shouldShowEditingControls(readOnly)) return null
273
288
 
274
- if (isMobile) {
275
- if (selectedReferenceId) {
276
- return <MobilePanelLayer isReference={true} node={null} panel={<ReferencePanel />} />
289
+ return (
290
+ <InspectedNodesContext.Provider value={inspectedIds}>
291
+ {renderPanel()}
292
+ </InspectedNodesContext.Provider>
293
+ )
294
+
295
+ function renderPanel() {
296
+ if (room && selectedIds.length === 0) return <RoomPanel room={room} />
297
+ if (selectedNodeType === 'floor-opening' && selectedIds[0])
298
+ return <FloorOpeningPanel key={selectedIds[0]} openingId={selectedIds[0]} />
299
+ if (zoneRoom && panelZoneId) {
300
+ const closeZone = () => setSelection({ selectedIds: [], zoneId: null })
301
+ return zoneRoom.record ? (
302
+ <RoomPanel onClose={closeZone} room={zoneRoom.record} />
303
+ ) : (
304
+ <OpenRoomPanel key={panelZoneId} onClose={closeZone} zoneId={panelZoneId} />
305
+ )
277
306
  }
278
- if (selectedIds.length > 1) {
307
+
308
+ if (isMobile) {
309
+ if (selectedReferenceId) {
310
+ return <MobilePanelLayer isReference={true} node={null} panel={<ReferencePanel />} />
311
+ }
312
+ if (selectedIds.length > 1) {
313
+ return (
314
+ <MobileMultiPanelLayer
315
+ breakdown={multiBreakdown}
316
+ panel={
317
+ homogeneousType ? (
318
+ <MultiParametricInspector footer={multiSelectionFooter} />
319
+ ) : (
320
+ <MultiSelectionPanel footer={multiSelectionFooter} />
321
+ )
322
+ }
323
+ type={homogeneousType}
324
+ />
325
+ )
326
+ }
279
327
  return (
280
- <MobileMultiPanelLayer
281
- breakdown={multiBreakdown}
282
- panel={
283
- homogeneousType ? (
284
- <MultiParametricInspector footer={multiSelectionFooter} />
285
- ) : (
286
- <MultiSelectionPanel footer={multiSelectionFooter} />
287
- )
288
- }
289
- type={homogeneousType}
328
+ <MobilePanelLayer
329
+ isReference={false}
330
+ node={selectedNode}
331
+ panel={panelForType(selectedNodeType)}
290
332
  />
291
333
  )
292
334
  }
293
- return (
294
- <MobilePanelLayer
295
- isReference={false}
296
- node={selectedNode}
297
- panel={panelForType(selectedNodeType)}
298
- />
299
- )
300
- }
301
335
 
302
- // Show reference panel if a reference is selected
303
- if (selectedReferenceId) {
304
- return <ReferencePanel />
305
- }
336
+ // Show reference panel if a reference is selected
337
+ if (selectedReferenceId) {
338
+ return <ReferencePanel />
339
+ }
306
340
 
307
- if (selectedZoneId && selectedIds.length === 0) {
308
- return (
309
- <ParametricInspector
310
- footer={inspectorFooter}
311
- key={selectedZoneId}
312
- nodeId={selectedZoneId as AnyNodeId}
313
- onClose={() => setSelection({ zoneId: null })}
314
- />
315
- )
316
- }
341
+ if (selectedZoneId && selectedIds.length === 0) {
342
+ return (
343
+ <ParametricInspector
344
+ footer={inspectorFooter}
345
+ key={selectedZoneId}
346
+ nodeId={selectedZoneId as AnyNodeId}
347
+ onClose={() => setSelection({ zoneId: null })}
348
+ />
349
+ )
350
+ }
317
351
 
318
- // Multi-selection: parametric inspector when every resolved id shares a type,
319
- // otherwise the actions-only panel. Mobile uses the same panels in a sheet.
320
- if (selectedIds.length > 1) {
321
- if (homogeneousType) {
322
- return <MultiParametricInspector footer={multiSelectionFooter} />
352
+ // Multi-selection: parametric inspector when every resolved id shares a type,
353
+ // otherwise the actions-only panel. Mobile uses the same panels in a sheet.
354
+ if (selectedIds.length > 1) {
355
+ if (homogeneousType) {
356
+ return <MultiParametricInspector footer={multiSelectionFooter} />
357
+ }
358
+ return <MultiSelectionPanel footer={multiSelectionFooter} />
323
359
  }
324
- return <MultiSelectionPanel footer={multiSelectionFooter} />
325
- }
326
360
 
327
- return panelForType(selectedNodeType, inspectorFooter)
361
+ return panelForType(selectedNodeType, inspectorFooter)
362
+ }
328
363
  }
@@ -12,7 +12,6 @@ import {
12
12
  } from '@pascal-app/core'
13
13
  import { useViewer } from '@pascal-app/viewer'
14
14
  import { ChevronDown, ChevronLeft, GripHorizontal, RotateCcw, X } from 'lucide-react'
15
- import Image from 'next/image'
16
15
  import {
17
16
  type ComponentType,
18
17
  createContext,
@@ -27,27 +26,25 @@ import {
27
26
  useState,
28
27
  } from 'react'
29
28
  import { useIsMobile } from '../../../hooks/use-mobile'
29
+ import { IconRefImage } from '../icon-ref'
30
+ import { NodeIconImage } from '../node-icon-image'
30
31
  import {
31
32
  resolveActiveExtension,
32
33
  toggleCard,
33
34
  toggleExtension,
34
35
  } from '../../../lib/inspector-card-mode'
35
36
  import { cn } from '../../../lib/utils'
37
+ import { useInspectorExpanded } from '../../../lib/inspector-expanded'
38
+ import { ActionButton, ActionGroup } from '../controls/action-button'
36
39
  import { PanelSection } from '../controls/panel-section'
37
40
  import { ErrorBoundary } from '../primitives/error-boundary'
41
+ import { useInRightStack } from '../right-stack'
42
+ import { CollectionsPopover } from './collections/collections-popover'
38
43
 
39
44
  const DRAG_MARGIN = 8
40
45
  // Pointer travel (px) below which a header press is treated as a click
41
46
  // (toggles collapse) rather than a drag.
42
47
  const CLICK_SLOP = 4
43
- let desktopInspectorCollapsed = true
44
-
45
- /** Forget the shared expanded state. Called when the last selection clears so
46
- * a fresh selection opens the inspector collapsed — the sharing is only meant
47
- * to survive swaps between panels (roof ↔ segment), not a close/reopen. */
48
- export function resetDesktopInspectorCollapsed() {
49
- desktopInspectorCollapsed = true
50
- }
51
48
 
52
49
  function clamp(value: number, min: number, max: number): number {
53
50
  return Math.min(Math.max(value, min), Math.max(min, max))
@@ -81,8 +78,19 @@ function getDragBounds(el: HTMLElement | null): {
81
78
  */
82
79
  export const InspectorFooterContext = createContext<React.ReactNode>(null)
83
80
 
81
+ /**
82
+ * The scene elements the inspector card shows (the selection, or the zone behind a room).
83
+ * `PanelManager` provides it so every card gets the Collections section; a card mounted
84
+ * elsewhere (the site panel in the sidebar) is not about the selection and gets none.
85
+ */
86
+ export const InspectedNodesContext = createContext<AnyNodeId[]>([])
87
+
84
88
  interface PanelWrapperProps {
85
89
  title: string
90
+ /** A short line above the title ("Room · Ground floor"). */
91
+ kicker?: string
92
+ /** Replaces the plain title, e.g. with the name edited in place. */
93
+ titleContent?: React.ReactNode
86
94
  /** Either a URL path (legacy panels pass `/icons/floor.webp` etc.,
87
95
  * rendered via next/image) OR a React node (registry-driven
88
96
  * inspector renders `<Icon icon="lucide:fence" />` from
@@ -100,6 +108,8 @@ interface PanelWrapperProps {
100
108
 
101
109
  export function PanelWrapper({
102
110
  title,
111
+ kicker,
112
+ titleContent,
103
113
  icon,
104
114
  onClose,
105
115
  onReset,
@@ -110,7 +120,9 @@ export function PanelWrapper({
110
120
  width = 320, // default width
111
121
  }: PanelWrapperProps) {
112
122
  const isMobile = useIsMobile()
123
+ const inStack = useInRightStack()
113
124
  const contextFooter = useContext(InspectorFooterContext)
125
+ const inspectedIds = useContext(InspectedNodesContext)
114
126
  const resolvedFooter = footer ?? contextFooter
115
127
 
116
128
  const panelRef = useRef<HTMLDivElement>(null)
@@ -126,7 +138,7 @@ export function PanelWrapper({
126
138
  ) as AnyNodeId | undefined
127
139
  // Subscribe to the selected node's *type* only — a string primitive that
128
140
  // doesn't change as fields are edited (same trick as ParametricInspector).
129
- const selectedType = useScene((s) => (selectedId ? (s.nodes[selectedId]?.type ?? null) : null))
141
+ const selectedType = useScene((s) => (selectedId ? s.nodes[selectedId]?.type ?? null : null))
130
142
  const installedPlugins = useScene((s) => s.installedPlugins)
131
143
  const extensions = useMemo(() => {
132
144
  // re-derive when plugin extensions register after mount (async plugin load)
@@ -143,24 +155,24 @@ export function PanelWrapper({
143
155
  // See `lib/inspector-card-mode.ts` for the transition table.
144
156
  const [activeExtensionId, setActiveExtensionId] = useState<string | null>(null)
145
157
  // Stale ids (kind changed, plugin gated off) fall back to regular mode.
146
- const activeExtension = resolveActiveExtension(activeExtensionId, extensions)
147
-
148
- // The whole panel is collapsed to just its header by default; the chevron
149
- // expands it to reveal the inspector body. Keep the desktop value shared
150
- // across inspector swaps (roof ↔ segment, etc.) so navigating between
151
- // related panels preserves whether the user left the inspector open.
152
- const [collapsed, setCollapsedState] = useState(desktopInspectorCollapsed)
153
-
154
- const setCollapsed = useCallback(
155
- (next: boolean | ((previous: boolean) => boolean)) => {
156
- setCollapsedState((previous) => {
157
- const resolved = typeof next === 'function' ? next(previous) : next
158
- desktopInspectorCollapsed = resolved
159
- return resolved
160
- })
161
- },
162
- [],
163
- )
158
+ const primaryExtensionId = useScene((state) => {
159
+ const node = selectedId ? state.nodes[selectedId] : undefined
160
+ return node ? extensions.find((extension) => extension.primaryWhen?.(node))?.id : undefined
161
+ })
162
+ const primaryExtension = extensions.find((extension) => extension.id === primaryExtensionId)
163
+ const activeExtension = primaryExtension ?? resolveActiveExtension(activeExtensionId, extensions)
164
+
165
+ // The panel is collapsed to just its header until the user expands it. The
166
+ // choice is one editor preference for every inspector (room, wall, item…),
167
+ // persisted, so every panel opens the way the user last left one.
168
+ const collapsed = !useInspectorExpanded((state) => state.expanded)
169
+ const setCollapsed = useCallback((next: boolean) => {
170
+ useInspectorExpanded.getState().setExpanded(!next)
171
+ }, [])
172
+
173
+ useEffect(() => {
174
+ if (primaryExtension) setCollapsed(false)
175
+ }, [primaryExtension, selectedId, setCollapsed])
164
176
 
165
177
  const applyMode = useCallback(
166
178
  (next: { collapsed: boolean; activeExtensionId: string | null }) => {
@@ -173,8 +185,12 @@ export function PanelWrapper({
173
185
  // Chevron / header press — collapsed → regular, regular → collapsed,
174
186
  // extension mode → regular (exit the extension first, stay expanded).
175
187
  const handleCardToggle = useCallback(() => {
188
+ if (primaryExtension) {
189
+ setCollapsed(!collapsed)
190
+ return
191
+ }
176
192
  applyMode(toggleCard({ collapsed, activeExtensionId }))
177
- }, [applyMode, collapsed, activeExtensionId])
193
+ }, [applyMode, collapsed, activeExtensionId, primaryExtension, setCollapsed])
178
194
 
179
195
  // Folding the card forgets the active extension — extension mode is a
180
196
  // one-shot affordance of the header icon, not sticky panel state.
@@ -205,8 +221,8 @@ export function PanelWrapper({
205
221
 
206
222
  const handleHeaderPointerDown = useCallback(
207
223
  (e: React.PointerEvent<HTMLDivElement>) => {
208
- // Buttons (close / reset / collapse) handle their own clicks.
209
- if ((e.target as HTMLElement).closest('button')) return
224
+ // Buttons (close / reset / collapse) and a title edited in place handle their own clicks.
225
+ if ((e.target as HTMLElement).closest('button, input, label')) return
210
226
  const rect = panelRef.current?.getBoundingClientRect()
211
227
  if (!rect) return
212
228
  const bounds = getDragBounds(panelRef.current)
@@ -279,9 +295,14 @@ export function PanelWrapper({
279
295
 
280
296
  return (
281
297
  <div
298
+ data-editor-inspector
282
299
  className={cn(
283
300
  isMobile
284
301
  ? 'flex h-full w-full flex-col overflow-hidden bg-transparent dark:text-foreground'
302
+ : inStack
303
+ ? // In the shared right column: it grows down and scrolls on itself,
304
+ // leaving the shortcuts card below it its room (`right-stack.tsx`).
305
+ 'pointer-events-auto flex max-h-full min-h-0 flex-[0_1_auto] flex-col overflow-hidden rounded-xl border border-border/50 bg-sidebar/95 shadow-2xl backdrop-blur-xl dark:text-foreground'
285
306
  // Cap height at `100dvh - 154px` so a tall panel's bottom edge
286
307
  // aligns flush with the top of the floating bottom action bar.
287
308
  // Combined with `top-20` (80px), the panel's bottom sits at
@@ -292,6 +313,7 @@ export function PanelWrapper({
292
313
  : 'pointer-events-auto fixed top-20 right-4 z-50 flex max-h-[calc(100dvh-154px)] flex-col overflow-hidden rounded-xl border border-border/50 bg-sidebar/95 shadow-2xl backdrop-blur-xl dark:text-foreground',
293
314
  className,
294
315
  )}
316
+ data-panel-wrapper
295
317
  ref={panelRef}
296
318
  style={
297
319
  isMobile
@@ -311,23 +333,24 @@ export function PanelWrapper({
311
333
  !collapsed && 'border-border/50 border-b',
312
334
  isDragging ? 'cursor-grabbing' : 'cursor-grab',
313
335
  )}
336
+ data-panel-header
314
337
  onPointerDown={handleHeaderPointerDown}
315
338
  onPointerMove={handleHeaderPointerMove}
316
339
  onPointerUp={handleHeaderPointerUp}
317
340
  >
318
- <div className="flex min-w-0 items-center gap-2">
341
+ <div className={cn('flex min-w-0 items-center gap-2', titleContent && 'flex-1 pr-2')}>
319
342
  {onBack && (
320
343
  <button
321
- className="mr-1 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
344
+ className="mr-1 flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground"
322
345
  onClick={onBack}
323
346
  type="button"
324
347
  >
325
348
  <ChevronLeft className="h-4 w-4" />
326
349
  </button>
327
350
  )}
328
- {icon &&
351
+ {primaryExtension ? renderExtensionIcon(primaryExtension.icon) : icon &&
329
352
  (typeof icon === 'string' ? (
330
- <Image
353
+ <NodeIconImage
331
354
  alt=""
332
355
  className="shrink-0 object-contain"
333
356
  height={16}
@@ -337,18 +360,35 @@ export function PanelWrapper({
337
360
  ) : (
338
361
  <span className="flex shrink-0 items-center justify-center">{icon}</span>
339
362
  ))}
340
- <h2 className="truncate font-semibold text-foreground text-sm tracking-tight">
341
- {title}
342
- </h2>
363
+ {kicker || titleContent ? (
364
+ <div className="flex min-w-0 flex-1 flex-col">
365
+ {kicker && (
366
+ <span className="truncate text-[11px] text-muted-foreground leading-tight" data-panel-kicker>
367
+ {kicker}
368
+ </span>
369
+ )}
370
+ {titleContent ?? (
371
+ <h2 className="truncate font-semibold text-foreground text-sm tracking-tight">
372
+ {primaryExtension?.title ?? title}
373
+ </h2>
374
+ )}
375
+ </div>
376
+ ) : (
377
+ <h2 className="truncate font-semibold text-foreground text-sm tracking-tight">
378
+ {primaryExtension?.title ?? title}
379
+ </h2>
380
+ )}
343
381
  </div>
344
382
 
345
- {/* Centered grip — purely a visual drag affordance. */}
346
- <GripHorizontal className="-translate-x-1/2 pointer-events-none absolute left-1/2 h-4 w-4 text-muted-foreground/40" />
383
+ {/* Centered grip — purely a visual drag affordance (a title edited in place takes its room). */}
384
+ {!titleContent && (
385
+ <GripHorizontal className="-translate-x-1/2 pointer-events-none absolute left-1/2 h-4 w-4 text-muted-foreground/40" />
386
+ )}
347
387
 
348
388
  <div className="flex items-center gap-1">
349
389
  {onReset && (
350
390
  <button
351
- className="flex h-7 w-7 items-center justify-center rounded-md bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
391
+ className="flex h-7 w-7 items-center justify-center rounded-md bg-black/4 dark:bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground"
352
392
  onClick={onReset}
353
393
  type="button"
354
394
  >
@@ -360,7 +400,7 @@ export function PanelWrapper({
360
400
  ONLY that extension's content (either/or with the regular
361
401
  controls); the active icon (highlighted) or the chevron
362
402
  returns to the regular controls. */}
363
- {extensions.map((extension) => {
403
+ {extensions.filter((extension) => extension !== primaryExtension).map((extension) => {
364
404
  const isActive = !collapsed && activeExtensionId === extension.id
365
405
  return (
366
406
  <button
@@ -369,8 +409,8 @@ export function PanelWrapper({
369
409
  className={cn(
370
410
  'flex h-7 w-7 items-center justify-center rounded-md transition-colors',
371
411
  isActive
372
- ? 'bg-cyan-500/20 text-cyan-400 hover:bg-cyan-500/30'
373
- : 'bg-[#2C2C2E] text-muted-foreground hover:bg-[#3e3e3e] hover:text-foreground',
412
+ ? 'bg-cyan-500/20 text-cyan-700 dark:text-cyan-400 hover:bg-cyan-500/30'
413
+ : 'bg-black/4 dark:bg-[#2C2C2E] text-muted-foreground hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground',
374
414
  )}
375
415
  key={extension.id}
376
416
  onClick={() =>
@@ -386,7 +426,7 @@ export function PanelWrapper({
386
426
  <button
387
427
  aria-expanded={!collapsed}
388
428
  aria-label={collapsed ? 'Expand panel' : 'Collapse panel'}
389
- className="flex h-7 w-7 items-center justify-center rounded-md bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
429
+ className="flex h-7 w-7 items-center justify-center rounded-md bg-black/4 dark:bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground"
390
430
  onClick={handleCardToggle}
391
431
  type="button"
392
432
  >
@@ -396,7 +436,7 @@ export function PanelWrapper({
396
436
  </button>
397
437
  {onClose && (
398
438
  <button
399
- className="flex h-7 w-7 items-center justify-center rounded-md bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-[#3e3e3e] hover:text-foreground"
439
+ className="flex h-7 w-7 items-center justify-center rounded-md bg-black/4 dark:bg-[#2C2C2E] text-muted-foreground transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] hover:text-foreground"
400
440
  onClick={onClose}
401
441
  type="button"
402
442
  >
@@ -413,8 +453,10 @@ export function PanelWrapper({
413
453
  controls (`children`). A stale extension id falls back to regular
414
454
  via `resolveActiveExtension`. */}
415
455
  {!(collapsed && !isMobile) && (
416
- <div className="no-scrollbar flex min-h-0 flex-1 flex-col overflow-y-auto">
417
- {!isMobile && selectedId && activeExtension ? (
456
+ <div className="no-scrollbar flex min-h-0 flex-1 flex-col overflow-y-auto" data-panel-scroll>
457
+ {selectedId && primaryExtension ? (
458
+ <InspectorExtensionContent extension={primaryExtension} nodeId={selectedId} />
459
+ ) : !isMobile && selectedId && activeExtension ? (
418
460
  <InspectorExtensionSection
419
461
  extension={activeExtension}
420
462
  key={activeExtension.id}
@@ -422,7 +464,23 @@ export function PanelWrapper({
422
464
  />
423
465
  ) : (
424
466
  <>
467
+ {/* The mobile sheet has its own header: a title edited in place moves into the body. */}
468
+ {isMobile && titleContent && (
469
+ <div className="flex flex-col px-4 pt-3">
470
+ {kicker && <span className="text-[11px] text-muted-foreground">{kicker}</span>}
471
+ {titleContent}
472
+ </div>
473
+ )}
425
474
  {children}
475
+ {inspectedIds.length > 0 && (
476
+ <PanelSection title="Collections">
477
+ <ActionGroup>
478
+ <CollectionsPopover nodeIds={inspectedIds}>
479
+ <ActionButton label="Manage collections…" />
480
+ </CollectionsPopover>
481
+ </ActionGroup>
482
+ </PanelSection>
483
+ )}
426
484
  {/* Mobile sheet has no header icons to swap modes — keep the
427
485
  plugin sections appended after the kind's controls there. */}
428
486
  {isMobile &&
@@ -453,7 +511,7 @@ export function PanelWrapper({
453
511
  * inspector's `renderIcon` (plain <img> so no next/image server deps). */
454
512
  function renderExtensionIcon(ref: IconRef): React.ReactNode {
455
513
  if (ref.kind === 'url') {
456
- return <img alt="" className="h-4 w-4 shrink-0 object-contain" src={ref.src} />
514
+ return <IconRefImage className="h-4 w-4 shrink-0" src={ref.src} />
457
515
  }
458
516
  if (ref.kind === 'iconify') {
459
517
  return <Icon height={16} icon={ref.name} width={16} />
@@ -509,23 +567,33 @@ function InspectorExtensionSection({
509
567
  defaultExpanded?: boolean
510
568
  extension: InspectorExtension
511
569
  nodeId: AnyNodeId
570
+ }) {
571
+ return (
572
+ <PanelSection defaultExpanded={defaultExpanded} title={extension.title}>
573
+ <InspectorExtensionContent extension={extension} nodeId={nodeId} />
574
+ </PanelSection>
575
+ )
576
+ }
577
+
578
+ function InspectorExtensionContent({
579
+ extension,
580
+ nodeId,
581
+ }: {
582
+ extension: InspectorExtension
583
+ nodeId: AnyNodeId
512
584
  }) {
513
585
  const node = useScene((s) => s.nodes[nodeId])
514
586
  if (!node) return null
515
587
  const Extension = resolveExtensionComponent(extension)
516
588
  return (
517
- <PanelSection defaultExpanded={defaultExpanded} title={extension.title}>
518
- <ErrorBoundary
519
- fallback={
520
- <p className="p-1 text-muted-foreground text-xs">
521
- “{extension.title}” hit an error and was unloaded for this session.
522
- </p>
523
- }
524
- >
525
- <Suspense fallback={null}>
526
- <Extension node={node} />
527
- </Suspense>
528
- </ErrorBoundary>
529
- </PanelSection>
589
+ <ErrorBoundary fallback={
590
+ <p className="p-1 text-muted-foreground text-xs">
591
+ “{extension.title}” hit an error and was unloaded for this session.
592
+ </p>
593
+ }>
594
+ <Suspense fallback={null}>
595
+ <Extension node={node} />
596
+ </Suspense>
597
+ </ErrorBoundary>
530
598
  )
531
599
  }
@@ -46,7 +46,7 @@ export function ParametricFieldControl({
46
46
  }
47
47
 
48
48
  case 'boolean': {
49
- const checked = !mixed && value === true
49
+ const checked = !mixed && (value ?? field.default) === true
50
50
  return (
51
51
  <ToggleControl
52
52
  checked={checked}
@@ -82,7 +82,7 @@ export function ParametricFieldControl({
82
82
  <div className="flex items-center justify-between px-3 py-2">
83
83
  <span className="text-foreground/80 text-xs">{prettifyKey(key)}</span>
84
84
  <select
85
- className="rounded-md border border-border/50 bg-[#2C2C2E] px-2 py-1 text-foreground text-xs focus:outline-none focus:ring-1 focus:ring-foreground/30"
85
+ className="rounded-md border border-border/50 bg-black/4 dark:bg-[#2C2C2E] px-2 py-1 text-foreground text-xs focus:outline-none focus:ring-1 focus:ring-foreground/30"
86
86
  onChange={(e) => apply(e.target.value)}
87
87
  value={mixed ? '' : str}
88
88
  >
@@ -119,7 +119,7 @@ export function ParametricFieldControl({
119
119
  value={str}
120
120
  />
121
121
  <input
122
- className="w-20 rounded-md border border-border/50 bg-[#2C2C2E] px-2 py-1 text-foreground text-xs focus:outline-none focus:ring-1 focus:ring-foreground/30"
122
+ className="w-20 rounded-md border border-border/50 bg-black/4 dark:bg-[#2C2C2E] px-2 py-1 text-foreground text-xs focus:outline-none focus:ring-1 focus:ring-foreground/30"
123
123
  onChange={(e) => apply(e.target.value)}
124
124
  type="text"
125
125
  value={mixed ? 'Mixed' : str}