@pascal-app/core 1.0.0-beta.4 → 1.0.0

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 (263) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/dist/capture/index.d.ts +3 -0
  4. package/dist/capture/index.d.ts.map +1 -0
  5. package/dist/capture/index.js +2 -0
  6. package/dist/capture/schema.d.ts +417 -0
  7. package/dist/capture/schema.d.ts.map +1 -0
  8. package/dist/capture/schema.js +348 -0
  9. package/dist/capture/source.d.ts +60 -0
  10. package/dist/capture/source.d.ts.map +1 -0
  11. package/dist/capture/source.js +254 -0
  12. package/dist/events/bus.d.ts +55 -7
  13. package/dist/events/bus.d.ts.map +1 -1
  14. package/dist/events/hidden-wall-pointer-hold.d.ts +28 -0
  15. package/dist/events/hidden-wall-pointer-hold.d.ts.map +1 -0
  16. package/dist/events/hidden-wall-pointer-hold.js +37 -0
  17. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +12 -0
  18. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  19. package/dist/hooks/spatial-grid/spatial-grid-manager.js +39 -1
  20. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +2785 -1
  21. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  22. package/dist/hooks/spatial-grid/spatial-grid-sync.js +132 -19
  23. package/dist/hooks/spatial-grid/support-host-patch.d.ts +45 -1
  24. package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -1
  25. package/dist/hooks/spatial-grid/support-host-patch.js +171 -6
  26. package/dist/index.d.ts +15 -6
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +17 -5
  29. package/dist/lib/asset-storage.d.ts.map +1 -1
  30. package/dist/lib/asset-storage.js +4 -1
  31. package/dist/lib/polygon-union.d.ts +4 -0
  32. package/dist/lib/polygon-union.d.ts.map +1 -0
  33. package/dist/lib/polygon-union.js +306 -0
  34. package/dist/lib/roof-overlap.d.ts +32 -0
  35. package/dist/lib/roof-overlap.d.ts.map +1 -0
  36. package/dist/lib/roof-overlap.js +72 -0
  37. package/dist/lib/room-topology-index.d.ts +50 -0
  38. package/dist/lib/room-topology-index.d.ts.map +1 -0
  39. package/dist/lib/room-topology-index.js +237 -0
  40. package/dist/lib/slab-polygon.d.ts +35 -2
  41. package/dist/lib/slab-polygon.d.ts.map +1 -1
  42. package/dist/lib/slab-polygon.js +133 -27
  43. package/dist/lib/space-detection.d.ts +40 -3
  44. package/dist/lib/space-detection.d.ts.map +1 -1
  45. package/dist/lib/space-detection.js +890 -246
  46. package/dist/registry/handles.d.ts +24 -3
  47. package/dist/registry/handles.d.ts.map +1 -1
  48. package/dist/registry/index.d.ts +3 -3
  49. package/dist/registry/index.d.ts.map +1 -1
  50. package/dist/registry/index.js +1 -1
  51. package/dist/registry/registry.d.ts +29 -6
  52. package/dist/registry/registry.d.ts.map +1 -1
  53. package/dist/registry/registry.js +111 -4
  54. package/dist/registry/scene-api.d.ts +16 -0
  55. package/dist/registry/scene-api.d.ts.map +1 -1
  56. package/dist/registry/scene-api.js +49 -1
  57. package/dist/registry/types.d.ts +261 -13
  58. package/dist/registry/types.d.ts.map +1 -1
  59. package/dist/registry/use-registry-version.d.ts +11 -0
  60. package/dist/registry/use-registry-version.d.ts.map +1 -0
  61. package/dist/registry/use-registry-version.js +15 -0
  62. package/dist/schema/__bench__/node-parsers.bench.d.ts +24 -0
  63. package/dist/schema/__bench__/node-parsers.bench.d.ts.map +1 -0
  64. package/dist/schema/__bench__/node-parsers.bench.js +314 -0
  65. package/dist/schema/__fixtures__/node-fixtures.d.ts +28 -0
  66. package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -0
  67. package/dist/schema/__fixtures__/node-fixtures.js +125 -0
  68. package/dist/schema/base.d.ts +1 -1
  69. package/dist/schema/base.d.ts.map +1 -1
  70. package/dist/schema/base.js +8 -1
  71. package/dist/schema/compiled-node-parsers.d.ts +40 -0
  72. package/dist/schema/compiled-node-parsers.d.ts.map +1 -0
  73. package/dist/schema/compiled-node-parsers.js +95 -0
  74. package/dist/schema/index.d.ts +20 -15
  75. package/dist/schema/index.d.ts.map +1 -1
  76. package/dist/schema/index.js +19 -12
  77. package/dist/schema/material.d.ts +2 -1
  78. package/dist/schema/material.d.ts.map +1 -1
  79. package/dist/schema/material.js +1 -0
  80. package/dist/schema/nodes/automatic-downspout.d.ts +17 -0
  81. package/dist/schema/nodes/automatic-downspout.d.ts.map +1 -0
  82. package/dist/schema/nodes/automatic-downspout.js +237 -0
  83. package/dist/schema/nodes/block.d.ts +107 -0
  84. package/dist/schema/nodes/block.d.ts.map +1 -0
  85. package/dist/schema/nodes/block.js +207 -0
  86. package/dist/schema/nodes/box-vent.d.ts +8 -2
  87. package/dist/schema/nodes/box-vent.d.ts.map +1 -1
  88. package/dist/schema/nodes/box-vent.js +2 -0
  89. package/dist/schema/nodes/building.d.ts +1 -1
  90. package/dist/schema/nodes/cabinet.d.ts +25 -3
  91. package/dist/schema/nodes/cabinet.d.ts.map +1 -1
  92. package/dist/schema/nodes/cabinet.js +26 -5
  93. package/dist/schema/nodes/ceiling.d.ts +1 -1
  94. package/dist/schema/nodes/chimney.d.ts +2 -2
  95. package/dist/schema/nodes/column.d.ts +6 -6
  96. package/dist/schema/nodes/construction-dimension.d.ts +4 -4
  97. package/dist/schema/nodes/cupola.d.ts +9 -1
  98. package/dist/schema/nodes/cupola.d.ts.map +1 -1
  99. package/dist/schema/nodes/cupola.js +2 -0
  100. package/dist/schema/nodes/door.d.ts +1 -1
  101. package/dist/schema/nodes/dormer.d.ts +44 -1
  102. package/dist/schema/nodes/dormer.d.ts.map +1 -1
  103. package/dist/schema/nodes/dormer.js +131 -7
  104. package/dist/schema/nodes/downspout.d.ts +11 -1
  105. package/dist/schema/nodes/downspout.d.ts.map +1 -1
  106. package/dist/schema/nodes/downspout.js +24 -0
  107. package/dist/schema/nodes/duct-fitting.d.ts +18 -1
  108. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -1
  109. package/dist/schema/nodes/duct-fitting.js +25 -4
  110. package/dist/schema/nodes/duct-segment.d.ts +23 -1
  111. package/dist/schema/nodes/duct-segment.d.ts.map +1 -1
  112. package/dist/schema/nodes/duct-segment.js +20 -0
  113. package/dist/schema/nodes/duct-terminal.d.ts +1 -1
  114. package/dist/schema/nodes/elevator.d.ts +1 -1
  115. package/dist/schema/nodes/eyebrow-vent.d.ts +8 -2
  116. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
  117. package/dist/schema/nodes/eyebrow-vent.js +2 -0
  118. package/dist/schema/nodes/fence.d.ts +1 -1
  119. package/dist/schema/nodes/guide.d.ts +1 -1
  120. package/dist/schema/nodes/gutter.d.ts +36 -2
  121. package/dist/schema/nodes/gutter.d.ts.map +1 -1
  122. package/dist/schema/nodes/gutter.js +308 -1
  123. package/dist/schema/nodes/hanger-overrides.d.ts +7 -0
  124. package/dist/schema/nodes/hanger-overrides.d.ts.map +1 -0
  125. package/dist/schema/nodes/hanger-overrides.js +6 -0
  126. package/dist/schema/nodes/hvac-equipment.d.ts +1 -1
  127. package/dist/schema/nodes/item.d.ts +4 -3
  128. package/dist/schema/nodes/item.d.ts.map +1 -1
  129. package/dist/schema/nodes/item.js +5 -0
  130. package/dist/schema/nodes/lean-to-extension.d.ts +212 -0
  131. package/dist/schema/nodes/lean-to-extension.d.ts.map +1 -0
  132. package/dist/schema/nodes/lean-to-extension.js +114 -0
  133. package/dist/schema/nodes/level.d.ts +5 -2
  134. package/dist/schema/nodes/level.d.ts.map +1 -1
  135. package/dist/schema/nodes/level.js +9 -0
  136. package/dist/schema/nodes/lineset.d.ts +1 -1
  137. package/dist/schema/nodes/liquid-line.d.ts +1 -1
  138. package/dist/schema/nodes/measurement.d.ts +1 -1
  139. package/dist/schema/nodes/pipe-fitting.d.ts +9 -1
  140. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -1
  141. package/dist/schema/nodes/pipe-fitting.js +6 -2
  142. package/dist/schema/nodes/pipe-segment.d.ts +23 -1
  143. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -1
  144. package/dist/schema/nodes/pipe-segment.js +20 -0
  145. package/dist/schema/nodes/pipe-trap.d.ts +1 -1
  146. package/dist/schema/nodes/ridge-vent.d.ts +1 -1
  147. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  148. package/dist/schema/nodes/ridge-vent.js +4 -1
  149. package/dist/schema/nodes/roof-segment-shape.d.ts +4 -0
  150. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -1
  151. package/dist/schema/nodes/roof-segment-shape.js +64 -1
  152. package/dist/schema/nodes/roof-segment-walls.d.ts +3 -1
  153. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -1
  154. package/dist/schema/nodes/roof-segment-walls.js +1 -1
  155. package/dist/schema/nodes/roof-segment.d.ts +41 -1
  156. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  157. package/dist/schema/nodes/roof-segment.js +79 -2
  158. package/dist/schema/nodes/roof.d.ts +22 -1
  159. package/dist/schema/nodes/roof.d.ts.map +1 -1
  160. package/dist/schema/nodes/roof.js +14 -0
  161. package/dist/schema/nodes/scan.d.ts +19 -2
  162. package/dist/schema/nodes/scan.d.ts.map +1 -1
  163. package/dist/schema/nodes/scan.js +11 -1
  164. package/dist/schema/nodes/shelf.d.ts +2 -2
  165. package/dist/schema/nodes/site.d.ts +1 -1
  166. package/dist/schema/nodes/skylight.d.ts +1 -1
  167. package/dist/schema/nodes/slab.d.ts +1 -1
  168. package/dist/schema/nodes/solar-panel.d.ts +1 -1
  169. package/dist/schema/nodes/spawn.d.ts +1 -1
  170. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  171. package/dist/schema/nodes/stair.d.ts +1 -1
  172. package/dist/schema/nodes/structural-grid.d.ts +1 -1
  173. package/dist/schema/nodes/turbine-vent.d.ts +7 -1
  174. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
  175. package/dist/schema/nodes/turbine-vent.js +2 -0
  176. package/dist/schema/nodes/wall.d.ts +4 -4
  177. package/dist/schema/nodes/wall.d.ts.map +1 -1
  178. package/dist/schema/nodes/wall.js +7 -1
  179. package/dist/schema/nodes/window.d.ts +10 -3
  180. package/dist/schema/nodes/window.d.ts.map +1 -1
  181. package/dist/schema/nodes/window.js +4 -0
  182. package/dist/schema/nodes/zone.d.ts +2 -2
  183. package/dist/schema/nodes/zone.js +1 -1
  184. package/dist/schema/types.d.ts +485 -109
  185. package/dist/schema/types.d.ts.map +1 -1
  186. package/dist/schema/types.js +27 -1
  187. package/dist/services/hosting.d.ts +2 -2
  188. package/dist/services/hosting.d.ts.map +1 -1
  189. package/dist/services/hosting.js +4 -4
  190. package/dist/services/index.d.ts +2 -2
  191. package/dist/services/index.d.ts.map +1 -1
  192. package/dist/services/index.js +2 -2
  193. package/dist/services/storey.d.ts +3 -10
  194. package/dist/services/storey.d.ts.map +1 -1
  195. package/dist/services/storey.js +45 -15
  196. package/dist/services/system-graph.d.ts +11 -0
  197. package/dist/services/system-graph.d.ts.map +1 -1
  198. package/dist/services/system-graph.js +35 -5
  199. package/dist/store/actions/node-actions.d.ts +15 -4
  200. package/dist/store/actions/node-actions.d.ts.map +1 -1
  201. package/dist/store/actions/node-actions.js +458 -19
  202. package/dist/store/history-control.d.ts +5 -0
  203. package/dist/store/history-control.d.ts.map +1 -1
  204. package/dist/store/history-control.js +68 -6
  205. package/dist/store/history-invalidation.d.ts +3 -0
  206. package/dist/store/history-invalidation.d.ts.map +1 -0
  207. package/dist/store/history-invalidation.js +72 -0
  208. package/dist/store/scene-hydration.d.ts +5 -0
  209. package/dist/store/scene-hydration.d.ts.map +1 -0
  210. package/dist/store/scene-hydration.js +57 -0
  211. package/dist/store/use-scene.d.ts +3 -0
  212. package/dist/store/use-scene.d.ts.map +1 -1
  213. package/dist/store/use-scene.js +407 -227
  214. package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
  215. package/dist/systems/elevator/elevator-service.js +16 -11
  216. package/dist/systems/roof/roof-elevation-system.d.ts +3 -0
  217. package/dist/systems/roof/roof-elevation-system.d.ts.map +1 -0
  218. package/dist/systems/roof/roof-elevation-system.js +94 -0
  219. package/dist/systems/roof/roof-elevation.d.ts +4 -0
  220. package/dist/systems/roof/roof-elevation.d.ts.map +1 -0
  221. package/dist/systems/roof/roof-elevation.js +95 -0
  222. package/dist/systems/roof/roof-footprint.d.ts +17 -0
  223. package/dist/systems/roof/roof-footprint.d.ts.map +1 -0
  224. package/dist/systems/roof/roof-footprint.js +83 -0
  225. package/dist/systems/slab/slab-support.d.ts +0 -31
  226. package/dist/systems/slab/slab-support.d.ts.map +1 -1
  227. package/dist/systems/slab/slab-support.js +26 -4
  228. package/dist/systems/stair/stair-flight.d.ts +17 -0
  229. package/dist/systems/stair/stair-flight.d.ts.map +1 -0
  230. package/dist/systems/stair/stair-flight.js +27 -0
  231. package/dist/systems/stair/stair-opening-preview.d.ts +1556 -1328
  232. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  233. package/dist/systems/stair/stair-opening-system.d.ts +1 -0
  234. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
  235. package/dist/systems/stair/stair-opening-system.js +93 -96
  236. package/dist/systems/stair/stair-rise.js +21 -20
  237. package/dist/systems/wall/wall-curve.d.ts +1 -0
  238. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  239. package/dist/systems/wall/wall-curve.js +73 -0
  240. package/dist/systems/wall/wall-footprint.d.ts +1 -0
  241. package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
  242. package/dist/systems/wall/wall-footprint.js +1 -0
  243. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  244. package/dist/systems/wall/wall-mitering.js +88 -9
  245. package/dist/systems/wall/wall-topology.d.ts +53 -0
  246. package/dist/systems/wall/wall-topology.d.ts.map +1 -0
  247. package/dist/systems/wall/wall-topology.js +414 -0
  248. package/dist/utils/clone-scene-graph.d.ts +2 -0
  249. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  250. package/dist/utils/clone-scene-graph.js +39 -2
  251. package/dist/utils/scene-migrations.d.ts +1 -0
  252. package/dist/utils/scene-migrations.d.ts.map +1 -1
  253. package/dist/utils/scene-migrations.js +1 -0
  254. package/dist/utils/vertical-scene-migration.d.ts +13 -0
  255. package/dist/utils/vertical-scene-migration.d.ts.map +1 -0
  256. package/dist/utils/vertical-scene-migration.js +179 -0
  257. package/dist/validation/validate-build-json.d.ts +3 -0
  258. package/dist/validation/validate-build-json.d.ts.map +1 -1
  259. package/dist/validation/validate-build-json.js +49 -2
  260. package/package.json +10 -3
  261. package/dist/systems/elevator/elevator-runtime-system.d.ts +0 -2
  262. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +0 -1
  263. package/dist/systems/elevator/elevator-runtime-system.js +0 -8
@@ -2,6 +2,7 @@ import type { ComponentType } from 'react';
2
2
  import type { AnimationClip, BufferGeometry, Object3D, Ray } from 'three';
3
3
  import type { ZodObject, z } from 'zod';
4
4
  import type { MaterialSchema, MaterialTarget } from '../schema/material';
5
+ import type { AssetInput, ItemNode } from '../schema/nodes/item';
5
6
  import type { MeasurementFeatureReference, MeasurementPoint } from '../schema/nodes/measurement';
6
7
  import type { SceneMaterial, SceneMaterialId } from '../schema/scene-material';
7
8
  import type { AnyNode, AnyNodeId } from '../schema/types';
@@ -60,6 +61,9 @@ export type GeometryContext = {
60
61
  materials?: Record<SceneMaterialId, SceneMaterial>;
61
62
  /** Opaque host/plugin context. Core never interprets extension values. */
62
63
  extensions?: Readonly<Record<string, unknown>>;
64
+ /** Read-only scene snapshot for pure floor-plan builders that need to
65
+ * inspect cross-kind spatial relationships such as connected ports. */
66
+ sceneNodes?: Readonly<Record<AnyNodeId, AnyNode>>;
63
67
  /**
64
68
  * Optional view state — only populated for `def.floorplan` builders. The
65
69
  * 2D floor-plan layer surfaces selection / hover here so kinds can vary
@@ -191,6 +195,8 @@ export type DimensionTextPosition = 'above' | 'centered';
191
195
  export type FloorplanStyle = {
192
196
  stroke?: string;
193
197
  fill?: string;
198
+ /** Winding rule for compound paths. `evenodd` keeps nested contour rings hollow. */
199
+ fillRule?: 'nonzero' | 'evenodd';
194
200
  strokeWidth?: number;
195
201
  strokeDasharray?: string;
196
202
  opacity?: number;
@@ -264,6 +270,8 @@ export type ToolHint = {
264
270
  * so the HUD reflects reality. Omit for always-shown hints.
265
271
  */
266
272
  minDraftVertices?: number;
273
+ /** Optional live predicate for hints that only apply in one tool sub-mode. */
274
+ visible?: ToolHintVisibility;
267
275
  /**
268
276
  * Render this hint as a live mode chip — like the snapping / continuation
269
277
  * chips — instead of a static key row: the HUD shows the current value's
@@ -273,6 +281,12 @@ export type ToolHint = {
273
281
  */
274
282
  chip?: ToolHintChip;
275
283
  };
284
+ export type ToolHintVisibility = {
285
+ /** Subscribe to changes that may alter `value`. */
286
+ subscribe: (onChange: () => void) => () => void;
287
+ /** Whether the helper should render this hint now. */
288
+ value: () => boolean;
289
+ };
276
290
  export type ToolHintChip = {
277
291
  /** Subscribe to live value changes (Zustand-store-like); returns unsubscribe. */
278
292
  subscribe: (onChange: () => void) => () => void;
@@ -288,6 +302,30 @@ export type ToolHintChip = {
288
302
  /** Hover tooltip, e.g. 'Placement type — click or press I to cycle'. */
289
303
  tooltip?: string;
290
304
  };
305
+ export type FloorplanScope = 'level' | 'building' | 'site';
306
+ export type ToolOptionChoice = {
307
+ /** Value token, e.g. 'draw'. */
308
+ value: string;
309
+ /** Button label. Sentence case. */
310
+ label: string;
311
+ /** Helper line shown under the row while this choice is active. */
312
+ description?: string;
313
+ };
314
+ export type ToolOption = {
315
+ /** Stable row id within the kind, e.g. 'footprintSource'. */
316
+ id: string;
317
+ /** Row label. Sentence case, e.g. 'Create from'. */
318
+ label: string;
319
+ choices: readonly ToolOptionChoice[];
320
+ /** Subscribe to live value changes (Zustand-store-like); returns unsubscribe. */
321
+ subscribe: (onChange: () => void) => () => void;
322
+ /** Current value token. */
323
+ value: () => string;
324
+ /** Select a choice. Pure state write — arming the tool is the host's job. */
325
+ set: (value: string) => void;
326
+ /** Optional live predicate — e.g. the roof's 'Create from' hides for conical. */
327
+ visible?: ToolHintVisibility;
328
+ };
291
329
  export type FloorplanGeometry = ({
292
330
  kind: 'path';
293
331
  d: string;
@@ -460,6 +498,7 @@ export type FloorplanGeometry = ({
460
498
  | {
461
499
  kind: 'midpoint-handle';
462
500
  point: FloorplanPoint;
501
+ activation?: 'drag' | 'action';
463
502
  affordance: string;
464
503
  payload: unknown;
465
504
  }
@@ -707,6 +746,8 @@ export type FloorplanAffordance<N> = {
707
746
  initialPlanPoint: FloorplanAffordancePoint;
708
747
  /** Active editor grid step in meters. */
709
748
  gridSnapStep: number;
749
+ /** Injected mutation/read seam for kind-owned affordances. */
750
+ sceneApi?: SceneApi;
710
751
  }): FloorplanAffordanceSession;
711
752
  };
712
753
  export type FloorplanMoveTargetSession = {
@@ -757,11 +798,47 @@ export type FloorplanMoveTargetSession = {
757
798
  export type FloorplanMoveTarget<N> = (args: {
758
799
  node: N;
759
800
  nodes: Record<AnyNodeId, AnyNode>;
801
+ sceneApi?: SceneApi;
760
802
  }) => FloorplanMoveTargetSession;
803
+ /**
804
+ * A plugin-contributed section for the floating node inspector card.
805
+ * When a node whose `type` is in `kinds` is selected, the inspector header
806
+ * shows the extension's `icon` as a button. Clicking it swaps the card
807
+ * body to ONLY this extension's `component` (inside a section titled
808
+ * `title`) — extension mode and the kind's own controls are EITHER/OR,
809
+ * never appended together. Clicking the (highlighted) icon again, or the
810
+ * chevron, returns to the regular controls. The mobile sheet has no
811
+ * header icons, so it appends the section after the kind's controls
812
+ * instead.
813
+ *
814
+ * `component` is lazy-loaded on first expand and receives the selected
815
+ * node as a `node` prop (`ComponentType<{ node: AnyNode }>` — typed as
816
+ * {@link LazyComponent} so plugin bundles don't need the host's node
817
+ * types to declare one).
818
+ *
819
+ * Extensions surface only when the contributing plugin is installed in
820
+ * the project (same `installedPlugins` gate as panels and node kinds).
821
+ */
822
+ export type InspectorExtension = {
823
+ /** Globally unique id, e.g. `pascal:bones:wall-engineering`. */
824
+ id: string;
825
+ /** The contributing plugin's id — used for the install gate. */
826
+ pluginId: string;
827
+ /** Node kinds whose inspector card grows this section. */
828
+ kinds: string[];
829
+ /** Header-button icon (16px box). */
830
+ icon: IconRef;
831
+ /** Section title, e.g. `Engineering`. */
832
+ title: string;
833
+ /** Lazy section body; receives `{ node }` (the selected node). */
834
+ component: LazyComponent;
835
+ };
761
836
  export type Plugin = {
762
837
  id: string;
763
838
  apiVersion: 1;
764
839
  nodes?: AnyNodeDefinition[];
840
+ /** Sections contributed to the floating node inspector card. */
841
+ inspectorExtensions?: InspectorExtension[];
765
842
  };
766
843
  export type AnyNodeDefinition = NodeDefinition<ZodObject<any>>;
767
844
  export type SurfaceRole = 'wall' | 'floor' | 'ceiling' | 'roof' | 'joinery' | 'glazing' | 'furnishing';
@@ -820,6 +897,13 @@ export type NodeDefinition<S extends ZodObject<any>> = {
820
897
  * Kinds outside any distribution system leave this unset.
821
898
  */
822
899
  distributionRole?: DistributionRole;
900
+ /** Optional behavior while the kind's click-to-click construction tool is active. */
901
+ drafting?: {
902
+ /** Raycast architectural hosts and emit their semantic surface data with grid events. */
903
+ surfaceQuery?: boolean;
904
+ /** Cancel the in-flight draft before applying an undo or redo history jump. */
905
+ cancelOnHistoryJump?: boolean;
906
+ };
823
907
  /**
824
908
  * When `distributionRole` is `'fitting'`, controls whether this fitting
825
909
  * is dragged as a rigid follower when a connected run endpoint moves.
@@ -851,6 +935,27 @@ export type NodeDefinition<S extends ZodObject<any>> = {
851
935
  dirtyTracking?: boolean;
852
936
  /** GLB bake treatment for this kind (default `'static'`). See {@link BakePolicy}. */
853
937
  bake?: BakePolicy;
938
+ /**
939
+ * Optional export-only geometry builder. The GLB exporter calls this against
940
+ * persisted scene data and replaces the registered node's cloned subtree
941
+ * with the returned local-space Object3D. The live editor object is never
942
+ * passed to the hook or mutated.
943
+ *
944
+ * Use this when the live geometry is unsuitable for a portable GLB (for
945
+ * example, a procedural NodeMaterial that masks a maximum candidate
946
+ * population on the GPU). The returned tree must be a complete static
947
+ * snapshot for this node and use exporter-supported Three.js materials.
948
+ */
949
+ bakeGeometry?: BakeGeometryBuilder<z.infer<S>>;
950
+ /**
951
+ * Optional asynchronous export-only geometry builder for textured static artifacts.
952
+ * Export preparation awaits this exactly once in place of {@link bakeGeometry}.
953
+ * Synchronous geometry-only callers continue to use `bakeGeometry`.
954
+ *
955
+ * The returned tree follows the same ownership contract: it is detached,
956
+ * local-space, complete for the node, and owned by the export artifact.
957
+ */
958
+ bakeGeometryAsync?: BakeGeometryAsyncBuilder<z.infer<S>>;
854
959
  /**
855
960
  * Renderer for this kind. Optional under the three-checkbox composition
856
961
  * model (see `wiki/architecture/node-definitions.md`): when omitted, the
@@ -944,8 +1049,9 @@ export type NodeDefinition<S extends ZodObject<any>> = {
944
1049
  /**
945
1050
  * Pure 2D builder for floor-plan rendering. Mirrors `geometry` but emits
946
1051
  * plain `FloorplanGeometry` data (SVG-renderable) rather than three.js
947
- * Object3D. Coordinates are level-local meters the floor-plan panel
948
- * applies the world→SVG transform.
1052
+ * Object3D. Level- and building-scoped builders emit building-local metres.
1053
+ * Site-scoped builders emit site-local metres; the floor-plan layer projects
1054
+ * their output into the active building's plan coordinates.
949
1055
  *
950
1056
  * Returns `null` when the kind shouldn't appear in floor plan (e.g. an
951
1057
  * invisible utility node, or a kind that's 3D-only). Kinds that need
@@ -970,8 +1076,11 @@ export type NodeDefinition<S extends ZodObject<any>> = {
970
1076
  * building). For `'building'`-scoped kinds the layer iterates every
971
1077
  * instance whose parent matches the active level's building, and
972
1078
  * synthesises a `GeometryContext` whose `parent` is the active level.
1079
+ * `'site'` discovers direct children of the active building's Site,
1080
+ * supplies the real Site as `ctx.parent`, and projects site-local output
1081
+ * into the active building's plan coordinates below level architecture.
973
1082
  */
974
- floorplanScope?: 'level' | 'building';
1083
+ floorplanScope?: FloorplanScope;
975
1084
  /**
976
1085
  * 2D drag affordances keyed by the string identifier emitted on
977
1086
  * `endpoint-handle` (and similar interactive floor-plan primitives) via
@@ -1135,6 +1244,12 @@ export type NodeDefinition<S extends ZodObject<any>> = {
1135
1244
  * its bespoke helper component instead.
1136
1245
  */
1137
1246
  toolHints?: ToolHint[];
1247
+ /**
1248
+ * Pick-one option rows for this kind's build tool, rendered by the shared
1249
+ * `<ToolOptionsPanel>` in whichever sidebar the host mounts it (see
1250
+ * `ToolOption`). E.g. the roof's 'Create from: Draw / Room'.
1251
+ */
1252
+ toolOptions?: readonly ToolOption[];
1138
1253
  /**
1139
1254
  * Which snapping profile this kind uses, so the editor's contextual snapping
1140
1255
  * HUD + snap math + force-place affordance are node-declared rather than
@@ -1250,6 +1365,8 @@ export type Presentation = {
1250
1365
  icon: IconRef;
1251
1366
  /** Tool palette section. Defaults to `category` when omitted. */
1252
1367
  paletteSection?: 'site' | 'structure' | 'furnish';
1368
+ /** Optional presentation-only subgroup used by palette surfaces. */
1369
+ paletteGroup?: string;
1253
1370
  /** Sort key within a palette section; lower numbers come first. */
1254
1371
  paletteOrder?: number;
1255
1372
  /** Set true for kinds that exist but should NOT appear in the palette
@@ -1258,6 +1375,9 @@ export type Presentation = {
1258
1375
  /** Set false when selection is edited directly through in-scene affordances
1259
1376
  * and the generic floating action menu would duplicate or conflict with them. */
1260
1377
  actionMenu?: boolean;
1378
+ /** Set false to drop the "Find in catalog" action for this kind — for nodes
1379
+ * that are placed through a plugin panel rather than a browsable catalog. */
1380
+ findInCatalog?: boolean;
1261
1381
  };
1262
1382
  export type IconRef =
1263
1383
  /** Iconify identifier, e.g. `lucide:square`. Matches the @iconify-react
@@ -1317,6 +1437,8 @@ export type BakeReplaceRenderer<N> = {
1317
1437
  }>;
1318
1438
  }>;
1319
1439
  };
1440
+ export type BakeGeometryBuilder<N> = (node: N, ctx: GeometryContext) => Object3D;
1441
+ export type BakeGeometryAsyncBuilder<N> = (node: N, ctx: GeometryContext) => Promise<Object3D>;
1320
1442
  export type AssetRef = {
1321
1443
  id: string;
1322
1444
  src: string;
@@ -1357,10 +1479,18 @@ export type Capabilities = {
1357
1479
  cuttable?: CuttableConfig;
1358
1480
  snappable?: SnappableConfig;
1359
1481
  surfaces?: SurfacesConfig;
1482
+ faceHost?: FaceHostCapability<any>;
1360
1483
  duplicable?: boolean | DuplicableConfig;
1361
1484
  deletable?: boolean;
1362
1485
  groupable?: boolean;
1363
1486
  selectable?: SelectableConfig;
1487
+ /**
1488
+ * Whether selecting this kind should replace its rendered mesh materials
1489
+ * with the editor's selection tint. Defaults to `true`. Set to `false` for
1490
+ * hidden interaction nodes whose rendered geometry must retain its authored
1491
+ * materials while the node remains selected (for example, paint layers).
1492
+ */
1493
+ selectionHighlight?: boolean;
1364
1494
  interactive?: boolean;
1365
1495
  floorPlaced?: FloorPlacedConfig;
1366
1496
  /**
@@ -1736,6 +1866,8 @@ export type CapabilityCtx = {
1736
1866
  export type MovableConfig = {
1737
1867
  axes: ReadonlyArray<'x' | 'y' | 'z'>;
1738
1868
  gridSnap?: boolean;
1869
+ /** Allow an ordinary primary-button body drag to enter the move tool. */
1870
+ directDrag?: boolean;
1739
1871
  /**
1740
1872
  * Pin the dragged node to the cursor (absolute placement) instead of the
1741
1873
  * default offset-preserving drag, where the node moves by the cursor's
@@ -1771,11 +1903,34 @@ export type MovableConfig = {
1771
1903
  parentFrame?: MovableParentFrame;
1772
1904
  /**
1773
1905
  * Optional group-move snap for the generic multi-selection translate gizmo.
1774
- * Returns an adjusted candidate position for this node when the moving group
1775
- * should magnetically settle onto a nearby feature (for example, a cabinet
1776
- * run snapping flush to a wall while the whole selected kitchen moves as one).
1906
+ * Returns an adjusted candidate position for this node when the moving
1907
+ * group should magnetically settle onto a nearby feature.
1777
1908
  */
1778
1909
  groupMoveSnap?: (args: GroupMoveSnapArgs) => [number, number, number] | null;
1910
+ /**
1911
+ * Optional rotation-aware group-move snap. This is additive to the original
1912
+ * `groupMoveSnap` contract so existing v1 plugins remain valid.
1913
+ */
1914
+ groupMoveSnapPose?: (args: GroupMoveSnapArgs) => GroupMoveSnapResult | null;
1915
+ /**
1916
+ * Optional kind-owned validity check for the final planar drag pose. This
1917
+ * complements `floorPlaced` collision checks for constraints that depend
1918
+ * on other scene geometry, such as a cabinet crossing a wall opening.
1919
+ */
1920
+ isValidPosition?: (args: {
1921
+ node: AnyNode;
1922
+ position: readonly [number, number, number];
1923
+ rotation: number;
1924
+ levelId: AnyNodeId | null;
1925
+ nodes: Readonly<Record<string, AnyNode>>;
1926
+ }) => boolean;
1927
+ /**
1928
+ * Kind-owned grid resolver for a planar move. Unlike scalar grid snapping,
1929
+ * this receives the complete candidate pose so a kind can snap a visible
1930
+ * footprint edge (including a local bounds offset and rotation) rather than
1931
+ * blindly rounding its stored origin.
1932
+ */
1933
+ gridSnapPosition?: (args: GridSnapPositionArgs) => [number, number, number];
1779
1934
  override?: (ctx: CapabilityCtx) => MovableConfig | null;
1780
1935
  };
1781
1936
  export type MovableParentFrame = {
@@ -1802,6 +1957,24 @@ export type MovableParentFrame = {
1802
1957
  magneticSnap?: (node: AnyNode, parent: AnyNode, local: readonly [number, number, number], nodes: Readonly<Record<string, AnyNode>>) => [number, number, number];
1803
1958
  /** Optional snap-line matches for the parent-frame magnetic snap result. */
1804
1959
  magneticSnapMatches?: (node: AnyNode, parent: AnyNode, local: readonly [number, number, number], snappedLocal: readonly [number, number, number], nodes: Readonly<Record<string, AnyNode>>) => ParentFrameSnapMatch[];
1960
+ /** Optional kind-owned live patches for derived nodes that follow the move. */
1961
+ previewOverrides?: (args: {
1962
+ node: AnyNode;
1963
+ parent: AnyNode;
1964
+ position: readonly [number, number, number];
1965
+ sceneApi: SceneApi;
1966
+ }) => ReadonlyArray<readonly [AnyNodeId, Partial<AnyNode>]>;
1967
+ /**
1968
+ * Optional live collision check for a child moving in the parent frame.
1969
+ * The generic move tool uses this to colour the drag bounds and reject an
1970
+ * invalid drop; the kind owns the actual domain rule.
1971
+ */
1972
+ isValidPosition?: (args: {
1973
+ node: AnyNode;
1974
+ parent: AnyNode;
1975
+ position: readonly [number, number, number];
1976
+ nodes: Readonly<Record<string, AnyNode>>;
1977
+ }) => boolean;
1805
1978
  /**
1806
1979
  * Called after a move of the child commits, with the LIVE (post-commit)
1807
1980
  * child and parent. Lets the kind run derived-state maintenance the
@@ -1825,10 +1998,19 @@ export type ParentFrameSnapMatch = {
1825
1998
  export type GroupMoveSnapArgs = {
1826
1999
  node: AnyNode;
1827
2000
  candidatePosition: [number, number, number];
2001
+ candidateRotation?: number;
1828
2002
  movingIds: readonly AnyNodeId[];
1829
2003
  nodes: Readonly<Record<string, AnyNode>>;
1830
2004
  levelId: AnyNodeId | null;
1831
2005
  };
2006
+ export type GroupMoveSnapResult = {
2007
+ position: [number, number, number];
2008
+ rotation?: number;
2009
+ };
2010
+ export type GridSnapPositionArgs = Omit<GroupMoveSnapArgs, 'candidateRotation'> & {
2011
+ candidateRotation: number;
2012
+ gridStep: number;
2013
+ };
1832
2014
  export type LiveTransformLike = {
1833
2015
  position: [number, number, number];
1834
2016
  rotation: number;
@@ -1862,7 +2044,9 @@ export type SnappableConfig = {
1862
2044
  export type SnapPointKind = 'start' | 'end' | 'midpoint' | 'center' | 'corners';
1863
2045
  export type SurfacesConfig = {
1864
2046
  top?: {
1865
- height: number | ((n: AnyNode) => number);
2047
+ height: number | ((n: AnyNode, context: {
2048
+ nodes: Record<string, AnyNode>;
2049
+ }) => number);
1866
2050
  };
1867
2051
  sides?: {
1868
2052
  faces: 'all' | ReadonlyArray<readonly [number, number, number]>;
@@ -1874,6 +2058,43 @@ export type SurfacePoint = {
1874
2058
  position: readonly [number, number, number];
1875
2059
  normal: readonly [number, number, number];
1876
2060
  };
2061
+ export type FaceHostPlacementArgs<N extends AnyNode = AnyNode> = {
2062
+ host: N;
2063
+ asset: AssetInput;
2064
+ draftItem: ItemNode | null;
2065
+ localPosition: readonly [number, number, number];
2066
+ faceIndex?: number;
2067
+ object: Object3D;
2068
+ currentFaceId?: string | null;
2069
+ rawDimensions: readonly [number, number, number];
2070
+ dimensions: readonly [number, number, number];
2071
+ snapScalar: (value: number) => number;
2072
+ };
2073
+ export type FaceHostStoredPlacementArgs<N extends AnyNode = AnyNode> = {
2074
+ host: N;
2075
+ item: ItemNode;
2076
+ position: readonly [number, number, number];
2077
+ };
2078
+ export type FaceHostStoredValidityArgs<N extends AnyNode = AnyNode> = {
2079
+ host: N;
2080
+ item: ItemNode;
2081
+ asset: AssetInput;
2082
+ };
2083
+ export type FaceHostPlacementResult = {
2084
+ faceId: string;
2085
+ nodeUpdate: Partial<ItemNode>;
2086
+ position: [number, number, number];
2087
+ rotation: [number, number, number];
2088
+ cursorPosition: [number, number, number];
2089
+ cursorRotation: [number, number, number];
2090
+ };
2091
+ export type FaceHostCapability<N extends AnyNode = AnyNode> = {
2092
+ currentFaceId: (item: ItemNode | null) => string | null;
2093
+ clearItemFields: readonly (keyof ItemNode)[];
2094
+ resolvePlacement: (args: FaceHostPlacementArgs<N>) => FaceHostPlacementResult | null;
2095
+ storedPlacementPatch: (args: FaceHostStoredPlacementArgs<N>) => Partial<ItemNode> | null;
2096
+ isStoredPlacementValid: (args: FaceHostStoredValidityArgs<N>) => boolean;
2097
+ };
1877
2098
  export type SelectableConfig = {
1878
2099
  hitVolume?: 'bbox' | 'mesh' | 'none';
1879
2100
  override?: (ctx: CapabilityCtx) => SelectableConfig | null;
@@ -1963,7 +2184,7 @@ export type ParametricDescriptor<N> = {
1963
2184
  * Direct store/MCP writes bypass it — keep real invariants in
1964
2185
  * `invariants`.
1965
2186
  */
1966
- derive?: (next: N, patch: Partial<N>) => Partial<N>;
2187
+ derive?: (next: N, patch: Partial<N>, previous?: N) => Partial<N>;
1967
2188
  /**
1968
2189
  * Cross-node companion to `derive`: after an inspector edit lands on
1969
2190
  * this node, return patches for OTHER nodes that must follow to keep
@@ -1983,13 +2204,15 @@ export type ParametricDescriptor<N> = {
1983
2204
  * auto-inserted elbow re-extends the duct runs it trimmed back onto the
1984
2205
  * corner it replaced. Called with the node and the live scene `nodes`
1985
2206
  * map BEFORE the deletion lands; patches targeting nodes also being
1986
- * deleted are ignored. Applied in the same `set` as the delete so it's
1987
- * one undo step. Fires only on `deleteNodes` (user-intent deletes)
2207
+ * deleted are ignored. `pendingDeleteIds` includes cascaded companion
2208
+ * deletes, while `requestedDeleteIds` is the user's original selection.
2209
+ * Applied in the same `set` as the delete so it's one undo step. Fires
2210
+ * only on `deleteNodes` (user-intent deletes) —
1988
2211
  * NOT on `applyNodeChanges`, whose deletes are internal re-routes that
1989
2212
  * rewrite neighbours explicitly in the same batch and would fight a
1990
2213
  * restore.
1991
2214
  */
1992
- onDelete?: (node: N, nodes: Record<AnyNodeId, AnyNode>) => Array<{
2215
+ onDelete?: (node: N, nodes: Record<AnyNodeId, AnyNode>, pendingDeleteIds: ReadonlySet<AnyNodeId>, requestedDeleteIds: ReadonlySet<AnyNodeId>) => Array<{
1993
2216
  id: AnyNodeId;
1994
2217
  data: Partial<AnyNode>;
1995
2218
  }>;
@@ -2000,9 +2223,10 @@ export type ParametricDescriptor<N> = {
2000
2223
  * deletion; returned ids are recursively expanded through the normal
2001
2224
  * descendant cascade. `pendingDeleteIds` holds every id already part of
2002
2225
  * the gesture so "would my parent become empty?" checks see sibling
2003
- * deletes from the same multi-select.
2226
+ * deletes from the same multi-select. `requestedDeleteIds` remains the
2227
+ * original selection while the pending set expands.
2004
2228
  */
2005
- onDeleteCascade?: (node: N, nodes: Record<AnyNodeId, AnyNode>, pendingDeleteIds: ReadonlySet<AnyNodeId>) => AnyNodeId[];
2229
+ onDeleteCascade?: (node: N, nodes: Record<AnyNodeId, AnyNode>, pendingDeleteIds: ReadonlySet<AnyNodeId>, requestedDeleteIds: ReadonlySet<AnyNodeId>) => AnyNodeId[];
2006
2230
  customPanel?: () => Promise<{
2007
2231
  default: ComponentType<{
2008
2232
  node: N;
@@ -2043,6 +2267,7 @@ export type ParamGroup<N> = {
2043
2267
  };
2044
2268
  export type ParamField<N> = {
2045
2269
  key: keyof N;
2270
+ label?: string;
2046
2271
  kind: 'number';
2047
2272
  unit?: string;
2048
2273
  min?: number;
@@ -2052,10 +2277,12 @@ export type ParamField<N> = {
2052
2277
  customEditor?: ComponentType;
2053
2278
  } | {
2054
2279
  key: keyof N;
2280
+ label?: string;
2055
2281
  kind: 'boolean';
2056
2282
  visibleIf?: (n: N) => boolean;
2057
2283
  } | {
2058
2284
  key: keyof N;
2285
+ label?: string;
2059
2286
  kind: 'enum';
2060
2287
  options: readonly string[];
2061
2288
  /** Defaults to 'select' (dropdown). 'segmented' renders the inline
@@ -2064,18 +2291,22 @@ export type ParamField<N> = {
2064
2291
  visibleIf?: (n: N) => boolean;
2065
2292
  } | {
2066
2293
  key: keyof N;
2294
+ label?: string;
2067
2295
  kind: 'vec3';
2068
2296
  visibleIf?: (n: N) => boolean;
2069
2297
  } | {
2070
2298
  key: keyof N;
2299
+ label?: string;
2071
2300
  kind: 'color';
2072
2301
  visibleIf?: (n: N) => boolean;
2073
2302
  } | {
2074
2303
  key: keyof N;
2304
+ label?: string;
2075
2305
  kind: 'material';
2076
2306
  visibleIf?: (n: N) => boolean;
2077
2307
  } | {
2078
2308
  key: keyof N;
2309
+ label?: string;
2079
2310
  kind: 'ref';
2080
2311
  refKind: string;
2081
2312
  visibleIf?: (n: N) => boolean;
@@ -2087,6 +2318,7 @@ export type ParamField<N> = {
2087
2318
  * update logic. `key` here is just a stable React key/label. */
2088
2319
  | {
2089
2320
  key: string;
2321
+ label?: string;
2090
2322
  kind: 'custom';
2091
2323
  component: ComponentType<{
2092
2324
  node: N;
@@ -2145,6 +2377,22 @@ export type SceneApi = {
2145
2377
  nodes: () => Readonly<Record<AnyNodeId, AnyNode>>;
2146
2378
  update: (id: AnyNodeId, patch: Partial<AnyNode>) => void;
2147
2379
  upsert: (node: AnyNode, parentId?: AnyNodeId) => AnyNodeId;
2380
+ createMany?: (ops: {
2381
+ node: AnyNode;
2382
+ parentId?: AnyNodeId;
2383
+ }[]) => void;
2384
+ applyChanges?: (changes: {
2385
+ create?: {
2386
+ node: AnyNode;
2387
+ parentId?: AnyNodeId;
2388
+ }[];
2389
+ update?: {
2390
+ id: AnyNodeId;
2391
+ data: Partial<AnyNode>;
2392
+ }[];
2393
+ delete?: AnyNodeId[];
2394
+ }) => void;
2395
+ subscribeNodes?: (listener: (nodes: Readonly<Record<AnyNodeId, AnyNode>>, previous: Readonly<Record<AnyNodeId, AnyNode>>, changedIds: ReadonlySet<AnyNodeId>) => void) => () => void;
2148
2396
  delete: (id: AnyNodeId) => void;
2149
2397
  restore: (id: AnyNodeId) => void;
2150
2398
  restoreAll: () => void;