@pascal-app/editor 0.8.0 → 0.9.2

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 (350) hide show
  1. package/package.json +15 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
  3. package/src/components/editor/bake-exporter.tsx +47 -0
  4. package/src/components/editor/custom-camera-controls.tsx +795 -28
  5. package/src/components/editor/editor-layout-mobile.tsx +8 -3
  6. package/src/components/editor/editor-layout-v2.tsx +76 -51
  7. package/src/components/editor/export-manager.tsx +69 -32
  8. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  9. package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
  10. package/src/components/editor/first-person/build-collider-world.ts +228 -83
  11. package/src/components/editor/first-person-controls.tsx +943 -27
  12. package/src/components/editor/floating-action-menu.tsx +676 -208
  13. package/src/components/editor/floating-building-action-menu.tsx +2 -2
  14. package/src/components/editor/floorplan-background-selection.test.ts +58 -0
  15. package/src/components/editor/floorplan-background-selection.ts +3 -2
  16. package/src/components/editor/floorplan-panel.tsx +6916 -12137
  17. package/src/components/editor/grid.tsx +197 -36
  18. package/src/components/editor/group-actions.ts +457 -0
  19. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  20. package/src/components/editor/group-move-3d.ts +397 -0
  21. package/src/components/editor/group-rotate-handle.tsx +420 -0
  22. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  23. package/src/components/editor/group-transform-shared.test.ts +396 -0
  24. package/src/components/editor/group-transform-shared.ts +489 -0
  25. package/src/components/editor/handles/handle-arrow.tsx +581 -0
  26. package/src/components/editor/handles/use-handle-drag.ts +246 -0
  27. package/src/components/editor/index.tsx +412 -162
  28. package/src/components/editor/measurement-pill.tsx +115 -0
  29. package/src/components/editor/node-action-menu.tsx +14 -1
  30. package/src/components/editor/node-arrow-handles.tsx +1646 -0
  31. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  32. package/src/components/editor/quick-measurement-card.tsx +70 -0
  33. package/src/components/editor/quick-measurement-hud.tsx +28 -0
  34. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  35. package/src/components/editor/selection-manager.tsx +1106 -483
  36. package/src/components/editor/site-edge-labels.tsx +45 -21
  37. package/src/components/editor/slab-hole-highlights.tsx +474 -0
  38. package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
  39. package/src/components/editor/three-context-bridge.ts +22 -0
  40. package/src/components/editor/thumbnail-generator.tsx +140 -41
  41. package/src/components/editor/use-floorplan-background-placement.ts +190 -35
  42. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  43. package/src/components/editor/use-floorplan-scene-data.ts +19 -7
  44. package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
  45. package/src/components/editor/wall-measurement-label.tsx +14 -19
  46. package/src/components/editor/wall-move-side-handles.tsx +870 -0
  47. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  48. package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
  49. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
  50. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  51. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  52. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  53. package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
  54. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  55. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
  56. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  57. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
  58. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
  59. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  60. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
  61. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  62. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  63. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
  64. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
  65. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  66. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
  69. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  70. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  71. package/src/components/editor-2d/svg-paths.ts +10 -2
  72. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
  73. package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
  74. package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
  75. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  76. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  77. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  78. package/src/components/systems/zone/zone-system.tsx +42 -13
  79. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  80. package/src/components/tools/elevator/elevator-tool.tsx +375 -0
  81. package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
  82. package/src/components/tools/fence/fence-drafting.ts +141 -27
  83. package/src/components/tools/item/move-tool.tsx +45 -105
  84. package/src/components/tools/item/placement-math.test.ts +22 -0
  85. package/src/components/tools/item/placement-math.ts +59 -7
  86. package/src/components/tools/item/placement-strategies.ts +432 -16
  87. package/src/components/tools/item/placement-types.ts +22 -1
  88. package/src/components/tools/item/use-draft-node.ts +51 -2
  89. package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
  90. package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
  91. package/src/components/tools/roof/roof-tool.tsx +350 -28
  92. package/src/components/tools/select/box-select-state.ts +88 -0
  93. package/src/components/tools/select/box-select-tool.tsx +442 -520
  94. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  95. package/src/components/tools/select/marquee-geometry.ts +155 -0
  96. package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
  97. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  98. package/src/components/tools/select/select-candidates.test.ts +109 -0
  99. package/src/components/tools/select/select-candidates.ts +85 -0
  100. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  101. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  102. package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
  103. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  104. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  105. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  106. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  107. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  108. package/src/components/tools/shared/placement-box.tsx +305 -0
  109. package/src/components/tools/shared/polygon-editor.tsx +866 -175
  110. package/src/components/tools/shared/segment-angle.ts +56 -0
  111. package/src/components/tools/site/site-boundary-editor.tsx +520 -16
  112. package/src/components/tools/site/site-flag-model.ts +18 -0
  113. package/src/components/tools/stair/stair-defaults.ts +2 -1
  114. package/src/components/tools/stair/stair-tool.tsx +342 -37
  115. package/src/components/tools/tool-manager.tsx +239 -62
  116. package/src/components/tools/wall/wall-drafting.test.ts +330 -0
  117. package/src/components/tools/wall/wall-drafting.ts +248 -171
  118. package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
  119. package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
  120. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  121. package/src/components/tools/zone/zone-tool.tsx +86 -82
  122. package/src/components/ui/action-menu/action-button.tsx +21 -1
  123. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  124. package/src/components/ui/action-menu/control-modes.tsx +40 -170
  125. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  126. package/src/components/ui/action-menu/index.tsx +3 -117
  127. package/src/components/ui/action-menu/measurement-control.tsx +188 -0
  128. package/src/components/ui/action-menu/structure-tools.tsx +27 -93
  129. package/src/components/ui/action-menu/view-toggles.tsx +279 -90
  130. package/src/components/ui/command-palette/editor-commands.tsx +20 -15
  131. package/src/components/ui/command-palette/index.tsx +5 -3
  132. package/src/components/ui/controls/material-paint-panel.tsx +152 -0
  133. package/src/components/ui/controls/material-picker.tsx +87 -177
  134. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  135. package/src/components/ui/controls/metric-control.tsx +142 -46
  136. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  137. package/src/components/ui/controls/slider-control.tsx +88 -38
  138. package/src/components/ui/floating-level-selector.tsx +47 -10
  139. package/src/components/ui/helpers/building-helper.tsx +10 -23
  140. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  141. package/src/components/ui/helpers/helper-manager.tsx +235 -23
  142. package/src/components/ui/helpers/item-helper.tsx +28 -32
  143. package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
  144. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  145. package/src/components/ui/icon-ref.tsx +47 -0
  146. package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
  147. package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
  148. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  149. package/src/components/ui/panels/node-display.ts +17 -16
  150. package/src/components/ui/panels/panel-manager.tsx +55 -58
  151. package/src/components/ui/panels/panel-wrapper.tsx +238 -12
  152. package/src/components/ui/panels/parametric-inspector.tsx +482 -0
  153. package/src/components/ui/panels/reference-panel.tsx +54 -19
  154. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  155. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  156. package/src/components/ui/primitives/sidebar.tsx +1 -0
  157. package/src/components/ui/scene-loader.tsx +1 -3
  158. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  159. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  160. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  161. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  162. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  163. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
  164. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
  165. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
  166. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  167. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
  168. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
  169. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  170. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  171. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
  172. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  173. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
  174. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
  175. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
  176. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  177. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  178. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
  179. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
  180. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
  181. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
  182. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
  183. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  184. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  185. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
  186. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  187. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  188. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  189. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  190. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  191. package/src/components/ui/sidebar/tab-bar.tsx +75 -1
  192. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  193. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  194. package/src/components/ui/snap-target-badge.tsx +88 -0
  195. package/src/components/viewer-overlay.tsx +253 -61
  196. package/src/components/viewer-zone-system.tsx +7 -2
  197. package/src/hooks/use-auto-save.test.ts +14 -0
  198. package/src/hooks/use-auto-save.ts +64 -10
  199. package/src/hooks/use-ceiling-events.ts +176 -0
  200. package/src/hooks/use-drag-action.ts +127 -0
  201. package/src/hooks/use-keyboard.ts +493 -95
  202. package/src/hooks/use-selection.ts +64 -0
  203. package/src/index.tsx +451 -6
  204. package/src/lib/active-placement-surface.ts +35 -0
  205. package/src/lib/ceiling-plan-snap.ts +24 -0
  206. package/src/lib/constants.ts +6 -2
  207. package/src/lib/contextual-help.test.ts +112 -0
  208. package/src/lib/contextual-help.ts +144 -0
  209. package/src/lib/continuation.ts +64 -0
  210. package/src/lib/direct-manipulation.test.ts +192 -0
  211. package/src/lib/direct-manipulation.ts +109 -0
  212. package/src/lib/editor-api.ts +50 -0
  213. package/src/lib/elevator-support.ts +96 -0
  214. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  215. package/src/lib/floorplan/apply-alignment.ts +117 -0
  216. package/src/lib/floorplan/floorplan-export.tsx +364 -0
  217. package/src/lib/floorplan/geometry.ts +53 -0
  218. package/src/lib/floorplan/index.ts +10 -0
  219. package/src/lib/floorplan/items.ts +5 -2
  220. package/src/lib/floorplan/plan-coords.ts +21 -0
  221. package/src/lib/floorplan/selection-tool.ts +40 -0
  222. package/src/lib/floorplan/stairs.ts +12 -1
  223. package/src/lib/fresh-planar-placement.test.ts +339 -0
  224. package/src/lib/fresh-planar-placement.ts +130 -0
  225. package/src/lib/glb-export.test.ts +441 -0
  226. package/src/lib/glb-export.ts +874 -0
  227. package/src/lib/history.ts +11 -1
  228. package/src/lib/interaction/hot-set.test.ts +133 -0
  229. package/src/lib/interaction/hot-set.ts +67 -0
  230. package/src/lib/interaction/overlay-policy.test.ts +51 -0
  231. package/src/lib/interaction/overlay-policy.ts +59 -0
  232. package/src/lib/interaction/scope.ts +211 -0
  233. package/src/lib/level-duplication.ts +54 -42
  234. package/src/lib/level-selection.ts +2 -2
  235. package/src/lib/material-paint.ts +227 -48
  236. package/src/lib/measurement-kind.test.ts +30 -0
  237. package/src/lib/measurement-kind.ts +19 -0
  238. package/src/lib/measurement-label.test.ts +47 -0
  239. package/src/lib/measurement-label.ts +68 -0
  240. package/src/lib/measurement-parser.ts +116 -0
  241. package/src/lib/measurements.test.ts +183 -0
  242. package/src/lib/measurements.ts +199 -0
  243. package/src/lib/paint-scope.test.ts +454 -0
  244. package/src/lib/paint-scope.ts +461 -0
  245. package/src/lib/placement-drag-release.ts +23 -0
  246. package/src/lib/placement-metadata.ts +29 -0
  247. package/src/lib/planar-cursor-placement.test.ts +100 -0
  248. package/src/lib/planar-cursor-placement.ts +42 -0
  249. package/src/lib/plugin-panels.test.ts +19 -0
  250. package/src/lib/plugin-panels.ts +91 -0
  251. package/src/lib/quick-measurement.test.ts +77 -0
  252. package/src/lib/quick-measurement.ts +109 -0
  253. package/src/lib/roof-duplication.ts +7 -7
  254. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  255. package/src/lib/roof-wall-hit.ts +164 -0
  256. package/src/lib/scene-clipboard.test.ts +196 -0
  257. package/src/lib/scene-clipboard.ts +332 -0
  258. package/src/lib/scene.ts +40 -8
  259. package/src/lib/selection-routing.test.ts +298 -0
  260. package/src/lib/selection-routing.ts +175 -0
  261. package/src/lib/sfx/index.ts +1 -0
  262. package/src/lib/sfx/movement-tick.test.ts +65 -0
  263. package/src/lib/sfx/movement-tick.ts +18 -0
  264. package/src/lib/sfx-bus.ts +69 -10
  265. package/src/lib/sfx-player.test.ts +124 -0
  266. package/src/lib/sfx-player.ts +148 -55
  267. package/src/lib/site-boundary.ts +1 -0
  268. package/src/lib/slab-plan-snap.test.ts +93 -0
  269. package/src/lib/slab-plan-snap.ts +108 -0
  270. package/src/lib/snapping-mode.test.ts +138 -0
  271. package/src/lib/snapping-mode.ts +179 -0
  272. package/src/lib/stair-duplication.ts +4 -4
  273. package/src/lib/stair-levels.test.ts +146 -0
  274. package/src/lib/stair-levels.ts +177 -0
  275. package/src/lib/surface-plan-snap.test.ts +71 -0
  276. package/src/lib/surface-plan-snap.ts +256 -0
  277. package/src/lib/use-linear-display.ts +40 -0
  278. package/src/lib/world-grid-snap.ts +231 -0
  279. package/src/lib/zone-content.ts +130 -0
  280. package/src/store/use-alignment-guides.ts +21 -0
  281. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  282. package/src/store/use-editor.tsx +767 -114
  283. package/src/store/use-facing-pose.ts +44 -0
  284. package/src/store/use-fence-curve-draft.ts +21 -0
  285. package/src/store/use-floorplan-draft-preview.ts +101 -0
  286. package/src/store/use-floorplan-marquee.ts +50 -0
  287. package/src/store/use-interaction-scope.test.ts +186 -0
  288. package/src/store/use-interaction-scope.ts +132 -0
  289. package/src/store/use-measurement-draft.test.ts +530 -0
  290. package/src/store/use-measurement-draft.ts +543 -0
  291. package/src/store/use-opening-guides.ts +41 -0
  292. package/src/store/use-placement-preview.ts +39 -0
  293. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  294. package/src/store/use-quick-measurement-hud.ts +77 -0
  295. package/src/store/use-segment-draft-chain.ts +28 -0
  296. package/src/store/use-stair-build-preview.ts +43 -0
  297. package/src/store/use-wall-move-ghosts.ts +36 -0
  298. package/src/store/use-wall-snap-indicator.ts +34 -0
  299. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  300. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  301. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  302. package/src/components/tools/building/move-building-tool.tsx +0 -157
  303. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  304. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  305. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  306. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  307. package/src/components/tools/column/column-tool.tsx +0 -97
  308. package/src/components/tools/column/move-column-tool.tsx +0 -105
  309. package/src/components/tools/door/door-math.ts +0 -110
  310. package/src/components/tools/door/door-tool.tsx +0 -324
  311. package/src/components/tools/door/move-door-tool.tsx +0 -412
  312. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  313. package/src/components/tools/fence/fence-tool.tsx +0 -346
  314. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  315. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  316. package/src/components/tools/item/item-tool.tsx +0 -26
  317. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  318. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  319. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  320. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  321. package/src/components/tools/slab/slab-tool.tsx +0 -322
  322. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  323. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  324. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  325. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  326. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  327. package/src/components/tools/wall/wall-tool.tsx +0 -332
  328. package/src/components/tools/window/move-window-tool.tsx +0 -447
  329. package/src/components/tools/window/window-math.ts +0 -117
  330. package/src/components/tools/window/window-tool.tsx +0 -332
  331. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  332. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  333. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  334. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  335. package/src/components/ui/panels/column-panel.tsx +0 -759
  336. package/src/components/ui/panels/door-panel.tsx +0 -1299
  337. package/src/components/ui/panels/fence-panel.tsx +0 -222
  338. package/src/components/ui/panels/item-panel.tsx +0 -306
  339. package/src/components/ui/panels/paint-panel.tsx +0 -163
  340. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  341. package/src/components/ui/panels/roof-panel.tsx +0 -280
  342. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  343. package/src/components/ui/panels/slab-panel.tsx +0 -250
  344. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  345. package/src/components/ui/panels/stair-panel.tsx +0 -577
  346. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  347. package/src/components/ui/panels/wall-panel.tsx +0 -273
  348. package/src/components/ui/panels/window-panel.tsx +0 -968
  349. package/src/contexts/presets-context.tsx +0 -121
  350. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -0,0 +1,218 @@
1
+ 'use client'
2
+
3
+ import { Icon } from '@iconify/react'
4
+ import { type IconRef, useScene } from '@pascal-app/core'
5
+ import { ChevronLeft, ChevronRight, ExternalLink, Puzzle } from 'lucide-react'
6
+ import { lazy, type ReactNode, Suspense, useState, useSyncExternalStore } from 'react'
7
+ import { editorHostPanelRegistry } from '../../../../lib/plugin-panels'
8
+ import { Button } from '../../primitives/button'
9
+
10
+ const PLUGIN_AUTHORING_URL =
11
+ 'https://editor.pascal.app/docs/developers/plugins'
12
+
13
+ function renderPluginIcon(ref: IconRef): ReactNode {
14
+ if (ref.kind === 'url') {
15
+ return <img alt="" className="h-8 w-8 object-contain" src={ref.src} />
16
+ }
17
+ if (ref.kind === 'iconify') {
18
+ return <Icon height={28} icon={ref.name} width={28} />
19
+ }
20
+ if (ref.kind === 'svg') {
21
+ return (
22
+ <svg height={28} viewBox={ref.viewBox} width={28}>
23
+ <path d={ref.path} fill="currentColor" />
24
+ </svg>
25
+ )
26
+ }
27
+ const LazyIcon = lazy(ref.module)
28
+ return (
29
+ <Suspense fallback={<Puzzle className="h-7 w-7" />}>
30
+ <LazyIcon />
31
+ </Suspense>
32
+ )
33
+ }
34
+
35
+ export function PluginsPanel() {
36
+ const [selectedPluginId, setSelectedPluginId] = useState<string | null>(null)
37
+ const panels = useSyncExternalStore(
38
+ editorHostPanelRegistry.subscribe,
39
+ editorHostPanelRegistry.getSnapshot,
40
+ editorHostPanelRegistry.getSnapshot,
41
+ )
42
+ const installedPlugins = useScene((state) => state.installedPlugins)
43
+ const setInstalledPlugins = useScene((state) => state.setInstalledPlugins)
44
+ const readOnly = useScene((state) => state.readOnly)
45
+ const plugins = Array.from(
46
+ new Map(
47
+ panels
48
+ .filter((panel) => panel.pluginId)
49
+ .map((panel) => [panel.pluginId as string, panel]),
50
+ ).entries(),
51
+ )
52
+ const selectedPlugin = selectedPluginId
53
+ ? plugins.find(([pluginId]) => pluginId === selectedPluginId)
54
+ : undefined
55
+
56
+ if (selectedPlugin) {
57
+ const [pluginId, panel] = selectedPlugin
58
+ const installed = installedPlugins.includes(pluginId)
59
+
60
+ return (
61
+ <div className="flex h-full flex-col overflow-y-auto p-4">
62
+ <div>
63
+ <Button
64
+ className="rounded-full"
65
+ onClick={() => setSelectedPluginId(null)}
66
+ size="sm"
67
+ variant="ghost"
68
+ >
69
+ <ChevronLeft className="h-4 w-4" />
70
+ All plugins
71
+ </Button>
72
+
73
+ <div className="mt-5 flex items-start gap-4">
74
+ <div className="flex h-16 w-16 shrink-0 items-center justify-center rounded-2xl bg-background/60">
75
+ {renderPluginIcon(panel.icon)}
76
+ </div>
77
+ <div className="min-w-0 pt-1">
78
+ <h2 className="font-semibold text-lg text-sidebar-foreground">{panel.label}</h2>
79
+ <p className="text-sidebar-foreground/50 text-sm">
80
+ {installed ? 'Installed' : 'Not installed'}
81
+ </p>
82
+ </div>
83
+ </div>
84
+
85
+ <p className="mt-5 text-sidebar-foreground/70 text-sm">
86
+ {panel.description ?? 'Adds a new tool panel to the editor.'}
87
+ </p>
88
+
89
+ <dl className="mt-6 divide-y divide-border/50 rounded-xl border border-border/60">
90
+ <div className="p-3">
91
+ <dt className="text-sidebar-foreground/50 text-xs">Plugin ID</dt>
92
+ <dd className="mt-1 break-all text-sidebar-foreground text-sm">{pluginId}</dd>
93
+ </div>
94
+ {panel.creator && (
95
+ <div className="p-3">
96
+ <dt className="text-sidebar-foreground/50 text-xs">Creator</dt>
97
+ <dd className="mt-1 text-sm">
98
+ {panel.creator.url ? (
99
+ <a
100
+ className="inline-flex items-center gap-1 text-sidebar-foreground underline-offset-4 hover:underline"
101
+ href={panel.creator.url}
102
+ rel="noreferrer"
103
+ target="_blank"
104
+ >
105
+ {panel.creator.name}
106
+ <ExternalLink className="h-3 w-3" />
107
+ </a>
108
+ ) : (
109
+ panel.creator.name
110
+ )}
111
+ </dd>
112
+ </div>
113
+ )}
114
+ {panel.pluginUrl && (
115
+ <div className="p-3">
116
+ <dt className="text-sidebar-foreground/50 text-xs">Plugin</dt>
117
+ <dd className="mt-1 text-sm">
118
+ <a
119
+ className="inline-flex items-center gap-1 text-sidebar-foreground underline-offset-4 hover:underline"
120
+ href={panel.pluginUrl}
121
+ rel="noreferrer"
122
+ target="_blank"
123
+ >
124
+ View plugin
125
+ <ExternalLink className="h-3 w-3" />
126
+ </a>
127
+ </dd>
128
+ </div>
129
+ )}
130
+ </dl>
131
+
132
+ <Button
133
+ className="mt-5 rounded-full"
134
+ disabled={readOnly}
135
+ onClick={() => {
136
+ const next = installed
137
+ ? installedPlugins.filter((id) => id !== pluginId)
138
+ : [...installedPlugins, pluginId]
139
+ setInstalledPlugins(next, { explicit: true })
140
+ }}
141
+ variant={installed ? 'outline' : 'default'}
142
+ >
143
+ {installed ? 'Uninstall' : 'Install'}
144
+ </Button>
145
+ </div>
146
+
147
+ <div className="mt-auto pt-6">
148
+ <a
149
+ className="inline-flex items-center gap-1.5 text-sidebar-foreground/70 text-sm underline-offset-4 hover:text-sidebar-foreground hover:underline"
150
+ href={PLUGIN_AUTHORING_URL}
151
+ rel="noreferrer"
152
+ target="_blank"
153
+ >
154
+ Create a Pascal plugin
155
+ <ExternalLink className="h-3.5 w-3.5" />
156
+ </a>
157
+ </div>
158
+ </div>
159
+ )
160
+ }
161
+
162
+ return (
163
+ <div className="flex h-full flex-col overflow-y-auto p-4">
164
+ <div className="mb-5">
165
+ <h2 className="font-semibold text-lg text-sidebar-foreground">Plugins</h2>
166
+ <p className="mt-1 text-sidebar-foreground/60 text-sm">
167
+ Add focused tools and content to this project.
168
+ </p>
169
+ </div>
170
+
171
+ <div className="flex flex-col gap-3">
172
+ {plugins.map(([pluginId, panel]) => {
173
+ const installed = installedPlugins.includes(pluginId)
174
+ return (
175
+ <button
176
+ className="w-full rounded-xl border border-border/60 bg-accent/20 p-3 text-left transition-colors hover:bg-accent/40"
177
+ key={pluginId}
178
+ onClick={() => setSelectedPluginId(pluginId)}
179
+ type="button"
180
+ >
181
+ <div className="flex items-start gap-3">
182
+ <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-background/60">
183
+ {renderPluginIcon(panel.icon)}
184
+ </div>
185
+ <div className="min-w-0 flex-1">
186
+ <div className="flex items-center justify-between gap-3">
187
+ <div>
188
+ <h3 className="font-medium text-sidebar-foreground">{panel.label}</h3>
189
+ <p className="text-sidebar-foreground/50 text-xs">
190
+ {installed ? 'Installed' : 'Not installed'}
191
+ </p>
192
+ </div>
193
+ <ChevronRight className="h-4 w-4 shrink-0 text-sidebar-foreground/50" />
194
+ </div>
195
+ <p className="mt-2 text-sidebar-foreground/60 text-sm">
196
+ {panel.description ?? 'Adds a new tool panel to the editor.'}
197
+ </p>
198
+ </div>
199
+ </div>
200
+ </button>
201
+ )
202
+ })}
203
+ </div>
204
+
205
+ <div className="mt-auto pt-6">
206
+ <a
207
+ className="inline-flex items-center gap-1.5 text-sidebar-foreground/70 text-sm underline-offset-4 hover:text-sidebar-foreground hover:underline"
208
+ href={PLUGIN_AUTHORING_URL}
209
+ rel="noreferrer"
210
+ target="_blank"
211
+ >
212
+ Create a Pascal plugin
213
+ <ExternalLink className="h-3.5 w-3.5" />
214
+ </a>
215
+ </div>
216
+ </div>
217
+ )
218
+ }
@@ -1,7 +1,7 @@
1
- import { emitter, useScene } from '@pascal-app/core'
1
+ import { clearSceneHistory, emitter, useScene, validateBuildJson } from '@pascal-app/core'
2
2
  import { useViewer } from '@pascal-app/viewer'
3
3
  import { TreeView, VisualJson } from '@visual-json/react'
4
- import { Camera, Download, Save, Trash2, Upload } from 'lucide-react'
4
+ import { Camera, Download, Map as MapIcon, Save, Trash2, Upload } from 'lucide-react'
5
5
  import {
6
6
  type KeyboardEvent,
7
7
  type SyntheticEvent,
@@ -10,6 +10,7 @@ import {
10
10
  useRef,
11
11
  useState,
12
12
  } from 'react'
13
+ import { exportFloorplanPdf } from '../../../../../lib/floorplan/floorplan-export'
13
14
  import { Button } from './../../../../../components/ui/primitives/button'
14
15
  import {
15
16
  Dialog,
@@ -21,6 +22,7 @@ import { Switch } from './../../../../../components/ui/primitives/switch'
21
22
  import useEditor, { selectDefaultBuildingAndLevel } from './../../../../../store/use-editor'
22
23
  import { AudioSettingsDialog } from './audio-settings-dialog'
23
24
  import { KeyboardShortcutsDialog } from './keyboard-shortcuts-dialog'
25
+ import { LoadBuildDialog, type PendingImport } from './load-build-dialog'
24
26
 
25
27
  type SceneNode = Record<string, unknown> & {
26
28
  id?: unknown
@@ -178,13 +180,15 @@ export function SettingsPanel({
178
180
  const fileInputRef = useRef<HTMLInputElement>(null)
179
181
  const nodes = useScene((state) => state.nodes)
180
182
  const rootNodeIds = useScene((state) => state.rootNodeIds)
183
+ const installedPlugins = useScene((state) => state.installedPlugins)
181
184
  const setScene = useScene((state) => state.setScene)
182
185
  const clearScene = useScene((state) => state.clearScene)
183
186
  const resetSelection = useViewer((state) => state.resetSelection)
184
187
  const exportScene = useViewer((state) => state.exportScene)
185
- const showGrid = useViewer((state) => state.showGrid)
188
+ const shadows = useViewer((state) => state.shadows)
186
189
  const setPhase = useEditor((state) => state.setPhase)
187
190
  const [isGeneratingThumbnail, setIsGeneratingThumbnail] = useState(false)
191
+ const [pendingImport, setPendingImport] = useState<PendingImport | null>(null)
188
192
  const sceneGraphValue = useMemo(
189
193
  () => buildSceneGraphValue(nodes as Record<string, SceneNode>, rootNodeIds),
190
194
  [nodes, rootNodeIds],
@@ -203,7 +207,7 @@ export function SettingsPanel({
203
207
  const isLocalProject = false // Props-based; only show cloud sections when projectId provided
204
208
 
205
209
  const handleSaveBuild = () => {
206
- const sceneData = { nodes, rootNodeIds }
210
+ const sceneData = { nodes, rootNodeIds, installedPlugins }
207
211
  const json = JSON.stringify(sceneData, null, 2)
208
212
  const blob = new Blob([json], { type: 'application/json' })
209
213
  const url = URL.createObjectURL(blob)
@@ -221,16 +225,37 @@ export function SettingsPanel({
221
225
 
222
226
  const reader = new FileReader()
223
227
  reader.onload = (event) => {
228
+ const text = event.target?.result as string
229
+ let parsed: unknown
224
230
  try {
225
- const data = JSON.parse(event.target?.result as string)
226
- if (data.nodes && data.rootNodeIds) {
227
- setScene(data.nodes, data.rootNodeIds)
228
- resetSelection()
229
- setPhase('site')
230
- }
231
- } catch (err) {
232
- console.error('Failed to load build:', err)
231
+ parsed = JSON.parse(text)
232
+ } catch {
233
+ setPendingImport({
234
+ fileName: file.name,
235
+ fileSizeBytes: file.size,
236
+ result: {
237
+ ok: false,
238
+ parsed: null,
239
+ stats: { total: 0, byType: {}, unknownTypes: {}, floorAreaM2: 0 },
240
+ errors: [
241
+ {
242
+ severity: 'error',
243
+ code: 'invalid_json',
244
+ message: 'File could not be parsed as JSON.',
245
+ },
246
+ ],
247
+ warnings: [],
248
+ schemaIssues: [],
249
+ schemaIssueCount: 0,
250
+ },
251
+ })
252
+ return
233
253
  }
254
+ setPendingImport({
255
+ fileName: file.name,
256
+ fileSizeBytes: file.size,
257
+ result: validateBuildJson(parsed),
258
+ })
234
259
  }
235
260
  reader.readAsText(file)
236
261
 
@@ -238,8 +263,34 @@ export function SettingsPanel({
238
263
  e.target.value = ''
239
264
  }
240
265
 
266
+ const handleConfirmImport = (parsed: {
267
+ nodes: Record<string, unknown>
268
+ rootNodeIds: string[]
269
+ installedPlugins?: string[]
270
+ }) => {
271
+ const currentScene = useScene.getState()
272
+ setScene(
273
+ parsed.nodes as Parameters<typeof setScene>[0],
274
+ parsed.rootNodeIds as Parameters<typeof setScene>[1],
275
+ {
276
+ installedPlugins: parsed.installedPlugins ?? currentScene.installedPlugins,
277
+ hasExplicitPluginInstallState:
278
+ parsed.installedPlugins !== undefined || currentScene.hasExplicitPluginInstallState,
279
+ },
280
+ )
281
+ // An import is a scene load: it becomes the undo floor. Without this,
282
+ // undo could step back into the pre-import scene state.
283
+ clearSceneHistory()
284
+ resetSelection()
285
+ setPhase('site')
286
+ setPendingImport(null)
287
+ }
288
+
241
289
  const handleResetToDefault = () => {
242
290
  clearScene()
291
+ // Same floor rule as import — undo after a reset must not resurrect the
292
+ // old scene (or land on the empty intermediate `unloadScene` state).
293
+ clearSceneHistory()
243
294
  resetSelection()
244
295
  setPhase('structure')
245
296
  selectDefaultBuildingAndLevel()
@@ -299,44 +350,68 @@ export function SettingsPanel({
299
350
  </div>
300
351
  <div className="flex items-center justify-between">
301
352
  <div>
302
- <div className="font-medium text-sm">Show Grid</div>
303
- <div className="text-muted-foreground text-xs">Visible only in the editor</div>
353
+ <div className="font-medium text-sm">Shadows</div>
354
+ <div className="text-muted-foreground text-xs">Cast shadows from lights</div>
304
355
  </div>
305
356
  <Switch
306
- checked={showGrid}
307
- onCheckedChange={(checked) => useViewer.getState().setShowGrid(checked)}
357
+ checked={shadows}
358
+ onCheckedChange={(checked) => useViewer.getState().setShadows(checked)}
308
359
  />
309
360
  </div>
310
361
  </div>
311
362
  )}
312
363
 
313
364
  {/* Export Section */}
314
- <div className="space-y-2">
365
+ <div className="space-y-4">
315
366
  <label className="font-medium text-muted-foreground text-xs uppercase">Export</label>
316
- <Button
317
- className="w-full justify-start gap-2"
318
- onClick={() => exportScene?.('glb')}
319
- variant="outline"
320
- >
321
- <Download className="size-4" />
322
- Export GLB
323
- </Button>
324
- <Button
325
- className="w-full justify-start gap-2"
326
- onClick={() => exportScene?.('stl')}
327
- variant="outline"
328
- >
329
- <Download className="size-4" />
330
- Export STL
331
- </Button>
332
- <Button
333
- className="w-full justify-start gap-2"
334
- onClick={() => exportScene?.('obj')}
335
- variant="outline"
336
- >
337
- <Download className="size-4" />
338
- Export OBJ
339
- </Button>
367
+
368
+ <div className="space-y-2">
369
+ <div className="font-medium text-muted-foreground text-xs">3D model</div>
370
+ <Button
371
+ className="w-full justify-start gap-2"
372
+ onClick={() => exportScene?.('glb')}
373
+ variant="outline"
374
+ >
375
+ <Download className="size-4" />
376
+ Export GLB
377
+ </Button>
378
+ <Button
379
+ className="w-full justify-start gap-2"
380
+ onClick={() => exportScene?.('stl')}
381
+ variant="outline"
382
+ >
383
+ <Download className="size-4" />
384
+ Export STL
385
+ </Button>
386
+ <Button
387
+ className="w-full justify-start gap-2"
388
+ onClick={() => exportScene?.('obj')}
389
+ variant="outline"
390
+ >
391
+ <Download className="size-4" />
392
+ Export OBJ
393
+ </Button>
394
+ </div>
395
+
396
+ <div className="space-y-2">
397
+ <div className="font-medium text-muted-foreground text-xs">Floorplan</div>
398
+ <Button
399
+ className="w-full justify-start gap-2"
400
+ onClick={() => exportFloorplanPdf('full')}
401
+ variant="outline"
402
+ >
403
+ <MapIcon className="size-4" />
404
+ Full floorplan
405
+ </Button>
406
+ <Button
407
+ className="w-full justify-start gap-2"
408
+ onClick={() => exportFloorplanPdf('structure')}
409
+ variant="outline"
410
+ >
411
+ <MapIcon className="size-4" />
412
+ Structure only
413
+ </Button>
414
+ </div>
340
415
  </div>
341
416
 
342
417
  {/* Thumbnail Section (only for cloud projects) */}
@@ -380,6 +455,12 @@ export function SettingsPanel({
380
455
  ref={fileInputRef}
381
456
  type="file"
382
457
  />
458
+
459
+ <LoadBuildDialog
460
+ onCancel={() => setPendingImport(null)}
461
+ onConfirm={handleConfirmImport}
462
+ pending={pendingImport}
463
+ />
383
464
  </div>
384
465
 
385
466
  {/* Audio Section */}
@@ -37,7 +37,6 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
37
37
  { keys: ['1'], action: 'Switch to Site phase' },
38
38
  { keys: ['2'], action: 'Switch to Structure phase' },
39
39
  { keys: ['3'], action: 'Switch to Furnish phase' },
40
- { keys: ['S'], action: 'Switch to Structure layer' },
41
40
  { keys: ['F'], action: 'Switch to Furnish layer' },
42
41
  { keys: ['Z'], action: 'Switch to Zones layer' },
43
42
  {
@@ -56,6 +55,8 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
56
55
  shortcuts: [
57
56
  { keys: ['V'], action: 'Switch to Select mode' },
58
57
  { keys: ['B'], action: 'Switch to Build mode' },
58
+ { keys: ['M'], action: 'Activate the last measurement tool' },
59
+ { keys: ['X'], action: 'Switch to Delete mode' },
59
60
  {
60
61
  keys: ['Esc'],
61
62
  action: 'Cancel the active tool and return to Select mode',
@@ -71,7 +72,48 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
71
72
  {
72
73
  keys: ['Cmd/Ctrl', 'Left click'],
73
74
  action: 'Add or remove an object from multi-selection',
74
- note: 'Works while in Select mode.',
75
+ note: 'Works in Select mode on the 3D canvas, the 2D floor plan, and the scene graph.',
76
+ },
77
+ {
78
+ keys: ['Shift', 'Left click'],
79
+ action: 'Add or remove an object from canvas multi-selection',
80
+ note: 'In the scene graph, Shift-click selects the visible range like a file browser.',
81
+ },
82
+ {
83
+ keys: ['Left click'],
84
+ action: 'Move the whole multi-selection',
85
+ note:
86
+ 'With 2+ objects selected, in 2D and 3D alike: drag the selection (or its dashed box) to slide it; click it to pick it up and place with the next click.',
87
+ },
88
+ {
89
+ keys: ['R', 'T'],
90
+ action: 'Rotate a multi-selection ±45° around its center',
91
+ note: 'Also works mid-move while carrying the selection.',
92
+ },
93
+ {
94
+ keys: ['Esc'],
95
+ action: 'Clear the selection',
96
+ note: 'Clicking empty space does the same.',
97
+ },
98
+ ],
99
+ },
100
+ {
101
+ title: 'Direct Manipulation',
102
+ shortcuts: [
103
+ {
104
+ keys: ['Cmd/Ctrl', 'Left click'],
105
+ action: 'Move the selected movable object under the cursor',
106
+ note: 'Drag in Select mode with a single object selected. Guided snapping and guides are enabled by default.',
107
+ },
108
+ {
109
+ keys: ['Cmd/Ctrl', 'Right click'],
110
+ action: 'Rotate the selected object under the cursor',
111
+ note: 'Drag left or right in Select mode with a single object selected. Rotation snaps to 15° increments by default.',
112
+ },
113
+ {
114
+ keys: ['Cmd/Ctrl', 'Shift', 'Right click'],
115
+ action: 'Rotate freely',
116
+ note: 'Hold Shift during the drag to bypass the 15° rotation increment.',
75
117
  },
76
118
  ],
77
119
  },
@@ -80,16 +122,27 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
80
122
  shortcuts: [
81
123
  {
82
124
  keys: ['Shift'],
83
- action: 'Temporarily disable angle snapping while drawing walls, slabs, and ceilings',
84
- note: 'Hold while drawing.',
125
+ action: 'Bypass guided snapping and angle constraints',
126
+ note: 'Hold during the active gesture. Passive guide or measurement feedback may stay visible.',
127
+ },
128
+ {
129
+ keys: ['Shift'],
130
+ action: 'Rotate freely, bypassing the default 15° rotation snap',
131
+ note: 'Hold while dragging a rotate handle or direct-rotation gesture.',
85
132
  },
86
133
  ],
87
134
  },
88
135
  {
89
136
  title: 'Item Placement',
90
137
  shortcuts: [
91
- { keys: ['R'], action: 'Rotate item clockwise, or toggle selected door open/closed' },
92
- { keys: ['T'], action: 'Rotate item counter-clockwise, or close selected door' },
138
+ {
139
+ keys: ['R', 'T'],
140
+ action: 'Rotate item; with a door selected, R toggles open/closed and T closes',
141
+ },
142
+ {
143
+ keys: ['E'],
144
+ action: 'Operate the selected node — doors, windows, and cabinet doors/drawers animate open/closed',
145
+ },
93
146
  {
94
147
  keys: ['Shift'],
95
148
  action: 'Temporarily bypass placement validation constraints',
@@ -100,6 +153,11 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
100
153
  {
101
154
  title: 'Camera',
102
155
  shortcuts: [
156
+ {
157
+ keys: ['W', 'A', 'S', 'D'],
158
+ action: 'Pan camera',
159
+ note: 'Moves in screen space, similar to dragging the camera view.',
160
+ },
103
161
  {
104
162
  keys: ['Middle click'],
105
163
  action: 'Pan camera',
@@ -152,7 +210,8 @@ export function KeyboardShortcutsDialog() {
152
210
  <DialogHeader className="shrink-0 border-b px-6 py-4">
153
211
  <DialogTitle>Keyboard Shortcuts</DialogTitle>
154
212
  <DialogDescription>
155
- Shortcuts are context-aware and depend on the current phase or tool.
213
+ Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift
214
+ during an active gesture to build freely.
156
215
  </DialogDescription>
157
216
  </DialogHeader>
158
217