@pascal-app/core 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 (471) hide show
  1. package/dist/events/bus.d.ts +67 -13
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/hooks/scene-registry/item-clip-registry.d.ts +18 -0
  4. package/dist/hooks/scene-registry/item-clip-registry.d.ts.map +1 -0
  5. package/dist/hooks/scene-registry/item-clip-registry.js +10 -0
  6. package/dist/hooks/scene-registry/scene-registry.d.ts +14 -23
  7. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  8. package/dist/hooks/scene-registry/scene-registry.js +59 -26
  9. package/dist/hooks/scene-registry/scene-registry.test.d.ts +2 -0
  10. package/dist/hooks/scene-registry/scene-registry.test.d.ts.map +1 -0
  11. package/dist/hooks/scene-registry/scene-registry.test.js +26 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  13. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  14. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  15. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  16. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  17. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +366 -0
  18. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +79 -11
  19. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  20. package/dist/hooks/spatial-grid/spatial-grid-manager.js +508 -141
  21. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  22. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  23. package/dist/hooks/spatial-grid/spatial-grid-sync.js +109 -18
  24. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
  25. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
  26. package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
  27. package/dist/index.d.ts +36 -8
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +33 -6
  30. package/dist/lib/level-name.d.ts +4 -0
  31. package/dist/lib/level-name.d.ts.map +1 -0
  32. package/dist/lib/level-name.js +10 -0
  33. package/dist/lib/measurement-geometry.d.ts +19 -0
  34. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  35. package/dist/lib/measurement-geometry.js +232 -0
  36. package/dist/lib/measurement-geometry.test.d.ts +2 -0
  37. package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
  38. package/dist/lib/measurement-geometry.test.js +102 -0
  39. package/dist/lib/polygon-geometry.d.ts +0 -1
  40. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  41. package/dist/lib/polygon-geometry.js +0 -18
  42. package/dist/lib/polygon-relations.d.ts +10 -0
  43. package/dist/lib/polygon-relations.d.ts.map +1 -0
  44. package/dist/lib/polygon-relations.js +73 -0
  45. package/dist/lib/selection-proxy.d.ts +4 -0
  46. package/dist/lib/selection-proxy.d.ts.map +1 -0
  47. package/dist/lib/selection-proxy.js +12 -0
  48. package/dist/lib/slab-polygon.d.ts +31 -2
  49. package/dist/lib/slab-polygon.d.ts.map +1 -1
  50. package/dist/lib/slab-polygon.js +525 -31
  51. package/dist/lib/slab-polygon.test.d.ts +2 -0
  52. package/dist/lib/slab-polygon.test.d.ts.map +1 -0
  53. package/dist/lib/slab-polygon.test.js +635 -0
  54. package/dist/lib/slots.d.ts +14 -0
  55. package/dist/lib/slots.d.ts.map +1 -0
  56. package/dist/lib/slots.js +26 -0
  57. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  58. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  59. package/dist/lib/space-detection-pause.test.js +42 -0
  60. package/dist/lib/space-detection.d.ts +54 -2
  61. package/dist/lib/space-detection.d.ts.map +1 -1
  62. package/dist/lib/space-detection.js +599 -124
  63. package/dist/lib/space-detection.test.d.ts +2 -0
  64. package/dist/lib/space-detection.test.d.ts.map +1 -0
  65. package/dist/lib/space-detection.test.js +376 -0
  66. package/dist/lib/wall-distance.d.ts +60 -0
  67. package/dist/lib/wall-distance.d.ts.map +1 -0
  68. package/dist/lib/wall-distance.js +85 -0
  69. package/dist/lib/zone-quantities.d.ts +21 -0
  70. package/dist/lib/zone-quantities.d.ts.map +1 -0
  71. package/dist/lib/zone-quantities.js +183 -0
  72. package/dist/lib/zone-quantities.test.d.ts +2 -0
  73. package/dist/lib/zone-quantities.test.d.ts.map +1 -0
  74. package/dist/lib/zone-quantities.test.js +82 -0
  75. package/dist/material-library.d.ts +20 -1
  76. package/dist/material-library.d.ts.map +1 -1
  77. package/dist/material-library.js +3737 -200
  78. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  79. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  80. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  81. package/dist/registry/handles.d.ts +394 -0
  82. package/dist/registry/handles.d.ts.map +1 -0
  83. package/dist/registry/handles.js +24 -0
  84. package/dist/registry/index.d.ts +7 -0
  85. package/dist/registry/index.d.ts.map +1 -0
  86. package/dist/registry/index.js +4 -0
  87. package/dist/registry/registry.d.ts +136 -0
  88. package/dist/registry/registry.d.ts.map +1 -0
  89. package/dist/registry/registry.js +282 -0
  90. package/dist/registry/registry.test.d.ts +2 -0
  91. package/dist/registry/registry.test.d.ts.map +1 -0
  92. package/dist/registry/registry.test.js +196 -0
  93. package/dist/registry/relations-resolver.d.ts +42 -0
  94. package/dist/registry/relations-resolver.d.ts.map +1 -0
  95. package/dist/registry/relations-resolver.js +77 -0
  96. package/dist/registry/relations-resolver.test.d.ts +2 -0
  97. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  98. package/dist/registry/relations-resolver.test.js +183 -0
  99. package/dist/registry/scene-api.d.ts +44 -0
  100. package/dist/registry/scene-api.d.ts.map +1 -0
  101. package/dist/registry/scene-api.js +111 -0
  102. package/dist/registry/scene-api.test.d.ts +2 -0
  103. package/dist/registry/scene-api.test.d.ts.map +1 -0
  104. package/dist/registry/scene-api.test.js +183 -0
  105. package/dist/registry/subtree.d.ts +67 -0
  106. package/dist/registry/subtree.d.ts.map +1 -0
  107. package/dist/registry/subtree.js +123 -0
  108. package/dist/registry/subtree.test.d.ts +2 -0
  109. package/dist/registry/subtree.test.d.ts.map +1 -0
  110. package/dist/registry/subtree.test.js +163 -0
  111. package/dist/registry/types.d.ts +2099 -0
  112. package/dist/registry/types.d.ts.map +1 -0
  113. package/dist/registry/types.js +1 -0
  114. package/dist/schema/index.d.ts +35 -4
  115. package/dist/schema/index.d.ts.map +1 -1
  116. package/dist/schema/index.js +32 -4
  117. package/dist/schema/material.d.ts +13 -2
  118. package/dist/schema/material.d.ts.map +1 -1
  119. package/dist/schema/material.js +17 -5
  120. package/dist/schema/material.test.d.ts +2 -0
  121. package/dist/schema/material.test.d.ts.map +1 -0
  122. package/dist/schema/material.test.js +43 -0
  123. package/dist/schema/nodes/box-vent.d.ts +74 -0
  124. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  125. package/dist/schema/nodes/box-vent.js +63 -0
  126. package/dist/schema/nodes/building.d.ts +1 -1
  127. package/dist/schema/nodes/building.d.ts.map +1 -1
  128. package/dist/schema/nodes/building.js +3 -2
  129. package/dist/schema/nodes/cabinet.d.ts +534 -0
  130. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  131. package/dist/schema/nodes/cabinet.js +137 -0
  132. package/dist/schema/nodes/ceiling.d.ts +5 -2
  133. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  134. package/dist/schema/nodes/ceiling.js +5 -1
  135. package/dist/schema/nodes/chimney.d.ts +158 -0
  136. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  137. package/dist/schema/nodes/chimney.js +67 -0
  138. package/dist/schema/nodes/column.d.ts +600 -7
  139. package/dist/schema/nodes/column.d.ts.map +1 -1
  140. package/dist/schema/nodes/column.js +589 -6
  141. package/dist/schema/nodes/cupola.d.ts +66 -0
  142. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  143. package/dist/schema/nodes/cupola.js +28 -0
  144. package/dist/schema/nodes/door.d.ts +13 -5
  145. package/dist/schema/nodes/door.d.ts.map +1 -1
  146. package/dist/schema/nodes/door.js +12 -0
  147. package/dist/schema/nodes/dormer.d.ts +228 -0
  148. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  149. package/dist/schema/nodes/dormer.js +119 -0
  150. package/dist/schema/nodes/downspout.d.ts +75 -0
  151. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  152. package/dist/schema/nodes/downspout.js +63 -0
  153. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  154. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  155. package/dist/schema/nodes/duct-fitting.js +92 -0
  156. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  157. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  158. package/dist/schema/nodes/duct-segment.js +73 -0
  159. package/dist/schema/nodes/duct-terminal.d.ts +57 -0
  160. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  161. package/dist/schema/nodes/duct-terminal.js +51 -0
  162. package/dist/schema/nodes/elevator.d.ts +106 -0
  163. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  164. package/dist/schema/nodes/elevator.js +57 -0
  165. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  166. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  167. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  168. package/dist/schema/nodes/fence.d.ts +19 -2
  169. package/dist/schema/nodes/fence.d.ts.map +1 -1
  170. package/dist/schema/nodes/fence.js +27 -2
  171. package/dist/schema/nodes/gutter.d.ts +84 -0
  172. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  173. package/dist/schema/nodes/gutter.js +76 -0
  174. package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
  175. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  176. package/dist/schema/nodes/hvac-equipment.js +55 -0
  177. package/dist/schema/nodes/item.d.ts +12 -0
  178. package/dist/schema/nodes/item.d.ts.map +1 -1
  179. package/dist/schema/nodes/item.js +20 -0
  180. package/dist/schema/nodes/level.d.ts +1 -1
  181. package/dist/schema/nodes/level.d.ts.map +1 -1
  182. package/dist/schema/nodes/level.js +4 -0
  183. package/dist/schema/nodes/lineset.d.ts +42 -0
  184. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  185. package/dist/schema/nodes/lineset.js +38 -0
  186. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  187. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  188. package/dist/schema/nodes/liquid-line.js +24 -0
  189. package/dist/schema/nodes/measurement.d.ts +306 -0
  190. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  191. package/dist/schema/nodes/measurement.js +87 -0
  192. package/dist/schema/nodes/measurement.test.d.ts +2 -0
  193. package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
  194. package/dist/schema/nodes/measurement.test.js +139 -0
  195. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  196. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  197. package/dist/schema/nodes/pipe-fitting.js +45 -0
  198. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  199. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  200. package/dist/schema/nodes/pipe-segment.js +36 -0
  201. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  202. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  203. package/dist/schema/nodes/pipe-trap.js +36 -0
  204. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +2 -0
  205. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
  206. package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
  207. package/dist/schema/nodes/ridge-vent.d.ts +82 -0
  208. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  209. package/dist/schema/nodes/ridge-vent.js +248 -0
  210. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  211. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  212. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  213. package/dist/schema/nodes/roof-segment-shape.test.d.ts +2 -0
  214. package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
  215. package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
  216. package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
  217. package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
  218. package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
  219. package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
  220. package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
  221. package/dist/schema/nodes/roof-segment-trim.test.js +107 -0
  222. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  223. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  224. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  225. package/dist/schema/nodes/roof-segment-walls.test.d.ts +2 -0
  226. package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
  227. package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
  228. package/dist/schema/nodes/roof-segment.d.ts +273 -3
  229. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  230. package/dist/schema/nodes/roof-segment.js +539 -3
  231. package/dist/schema/nodes/roof.d.ts +8 -8
  232. package/dist/schema/nodes/roof.d.ts.map +1 -1
  233. package/dist/schema/nodes/roof.js +4 -16
  234. package/dist/schema/nodes/shelf.d.ts +106 -0
  235. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  236. package/dist/schema/nodes/shelf.js +90 -0
  237. package/dist/schema/nodes/site.d.ts +1 -122
  238. package/dist/schema/nodes/site.d.ts.map +1 -1
  239. package/dist/schema/nodes/site.js +2 -6
  240. package/dist/schema/nodes/skylight.d.ts +279 -0
  241. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  242. package/dist/schema/nodes/skylight.js +152 -0
  243. package/dist/schema/nodes/slab.d.ts +5 -2
  244. package/dist/schema/nodes/slab.d.ts.map +1 -1
  245. package/dist/schema/nodes/slab.js +5 -1
  246. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  247. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  248. package/dist/schema/nodes/solar-panel.js +47 -0
  249. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  250. package/dist/schema/nodes/stair.d.ts +13 -12
  251. package/dist/schema/nodes/stair.d.ts.map +1 -1
  252. package/dist/schema/nodes/stair.js +3 -0
  253. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  254. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  255. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  256. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  257. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  258. package/dist/schema/nodes/turbine-vent.js +41 -0
  259. package/dist/schema/nodes/wall.d.ts +245 -6
  260. package/dist/schema/nodes/wall.d.ts.map +1 -1
  261. package/dist/schema/nodes/wall.js +257 -0
  262. package/dist/schema/nodes/wall.test.d.ts +2 -0
  263. package/dist/schema/nodes/wall.test.d.ts.map +1 -0
  264. package/dist/schema/nodes/wall.test.js +208 -0
  265. package/dist/schema/nodes/window.d.ts +11 -3
  266. package/dist/schema/nodes/window.d.ts.map +1 -1
  267. package/dist/schema/nodes/window.js +12 -0
  268. package/dist/schema/nodes/zone.d.ts +2 -0
  269. package/dist/schema/nodes/zone.d.ts.map +1 -1
  270. package/dist/schema/nodes/zone.js +6 -0
  271. package/dist/schema/scene-material.d.ts +41 -0
  272. package/dist/schema/scene-material.d.ts.map +1 -0
  273. package/dist/schema/scene-material.js +9 -0
  274. package/dist/schema/types.d.ts +2850 -655
  275. package/dist/schema/types.d.ts.map +1 -1
  276. package/dist/schema/types.js +49 -0
  277. package/dist/services/alignment-anchors.d.ts +100 -0
  278. package/dist/services/alignment-anchors.d.ts.map +1 -0
  279. package/dist/services/alignment-anchors.js +312 -0
  280. package/dist/services/alignment-anchors.test.d.ts +2 -0
  281. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  282. package/dist/services/alignment-anchors.test.js +383 -0
  283. package/dist/services/alignment.d.ts +131 -0
  284. package/dist/services/alignment.d.ts.map +1 -0
  285. package/dist/services/alignment.js +171 -0
  286. package/dist/services/alignment.test.d.ts +2 -0
  287. package/dist/services/alignment.test.d.ts.map +1 -0
  288. package/dist/services/alignment.test.js +92 -0
  289. package/dist/services/drag-session.d.ts +47 -0
  290. package/dist/services/drag-session.d.ts.map +1 -0
  291. package/dist/services/drag-session.js +100 -0
  292. package/dist/services/drag-session.test.d.ts +2 -0
  293. package/dist/services/drag-session.test.d.ts.map +1 -0
  294. package/dist/services/drag-session.test.js +219 -0
  295. package/dist/services/hosting.d.ts +88 -0
  296. package/dist/services/hosting.d.ts.map +1 -0
  297. package/dist/services/hosting.js +131 -0
  298. package/dist/services/hosting.test.d.ts +2 -0
  299. package/dist/services/hosting.test.d.ts.map +1 -0
  300. package/dist/services/hosting.test.js +230 -0
  301. package/dist/services/index.d.ts +13 -0
  302. package/dist/services/index.d.ts.map +1 -0
  303. package/dist/services/index.js +12 -0
  304. package/dist/services/level-height.d.ts +28 -0
  305. package/dist/services/level-height.d.ts.map +1 -0
  306. package/dist/services/level-height.js +62 -0
  307. package/dist/services/movement.d.ts +48 -0
  308. package/dist/services/movement.d.ts.map +1 -0
  309. package/dist/services/movement.js +65 -0
  310. package/dist/services/movement.test.d.ts +2 -0
  311. package/dist/services/movement.test.d.ts.map +1 -0
  312. package/dist/services/movement.test.js +133 -0
  313. package/dist/services/opening-guides.d.ts +141 -0
  314. package/dist/services/opening-guides.d.ts.map +1 -0
  315. package/dist/services/opening-guides.js +267 -0
  316. package/dist/services/opening-guides.test.d.ts +2 -0
  317. package/dist/services/opening-guides.test.d.ts.map +1 -0
  318. package/dist/services/opening-guides.test.js +208 -0
  319. package/dist/services/port-connectivity.d.ts +115 -0
  320. package/dist/services/port-connectivity.d.ts.map +1 -0
  321. package/dist/services/port-connectivity.js +300 -0
  322. package/dist/services/port-connectivity.test.d.ts +2 -0
  323. package/dist/services/port-connectivity.test.d.ts.map +1 -0
  324. package/dist/services/port-connectivity.test.js +297 -0
  325. package/dist/services/riser-diagram.d.ts +40 -0
  326. package/dist/services/riser-diagram.d.ts.map +1 -0
  327. package/dist/services/riser-diagram.js +110 -0
  328. package/dist/services/riser-diagram.test.d.ts +2 -0
  329. package/dist/services/riser-diagram.test.d.ts.map +1 -0
  330. package/dist/services/riser-diagram.test.js +66 -0
  331. package/dist/services/single-undo-dance.test.d.ts +2 -0
  332. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  333. package/dist/services/single-undo-dance.test.js +210 -0
  334. package/dist/services/snap.d.ts +80 -0
  335. package/dist/services/snap.d.ts.map +1 -0
  336. package/dist/services/snap.js +140 -0
  337. package/dist/services/snap.test.d.ts +2 -0
  338. package/dist/services/snap.test.d.ts.map +1 -0
  339. package/dist/services/snap.test.js +137 -0
  340. package/dist/services/system-graph.d.ts +30 -0
  341. package/dist/services/system-graph.d.ts.map +1 -0
  342. package/dist/services/system-graph.js +164 -0
  343. package/dist/services/system-graph.test.d.ts +2 -0
  344. package/dist/services/system-graph.test.d.ts.map +1 -0
  345. package/dist/services/system-graph.test.js +138 -0
  346. package/dist/services/validate-dwv.d.ts +32 -0
  347. package/dist/services/validate-dwv.d.ts.map +1 -0
  348. package/dist/services/validate-dwv.js +118 -0
  349. package/dist/services/validate-dwv.test.d.ts +2 -0
  350. package/dist/services/validate-dwv.test.d.ts.map +1 -0
  351. package/dist/services/validate-dwv.test.js +109 -0
  352. package/dist/solar-panel-presets.d.ts +17 -0
  353. package/dist/solar-panel-presets.d.ts.map +1 -0
  354. package/dist/solar-panel-presets.js +43 -0
  355. package/dist/store/actions/node-actions.d.ts +14 -2
  356. package/dist/store/actions/node-actions.d.ts.map +1 -1
  357. package/dist/store/actions/node-actions.js +585 -15
  358. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  359. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  360. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  361. package/dist/store/actions/reparent.test.d.ts +2 -0
  362. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  363. package/dist/store/actions/reparent.test.js +212 -0
  364. package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
  365. package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
  366. package/dist/store/actions/ridge-vent-update.test.js +264 -0
  367. package/dist/store/history-control.d.ts +11 -0
  368. package/dist/store/history-control.d.ts.map +1 -1
  369. package/dist/store/history-control.js +33 -0
  370. package/dist/store/use-interactive.d.ts +39 -0
  371. package/dist/store/use-interactive.d.ts.map +1 -1
  372. package/dist/store/use-interactive.js +75 -0
  373. package/dist/store/use-live-node-overrides.d.ts +22 -0
  374. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  375. package/dist/store/use-live-node-overrides.js +59 -0
  376. package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
  377. package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
  378. package/dist/store/use-scene-dirty-tracking.test.js +68 -0
  379. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  380. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  381. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  382. package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
  383. package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
  384. package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
  385. package/dist/store/use-scene-plugins.test.d.ts +2 -0
  386. package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
  387. package/dist/store/use-scene-plugins.test.js +51 -0
  388. package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
  389. package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
  390. package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
  391. package/dist/store/use-scene-window-migration.test.d.ts +2 -0
  392. package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
  393. package/dist/store/use-scene-window-migration.test.js +85 -0
  394. package/dist/store/use-scene.d.ts +28 -2
  395. package/dist/store/use-scene.d.ts.map +1 -1
  396. package/dist/store/use-scene.js +546 -49
  397. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  398. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  399. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  400. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  401. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  402. package/dist/systems/elevator/elevator-geometry.js +50 -0
  403. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  404. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  405. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  406. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  407. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  408. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  409. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  410. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  411. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  412. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  413. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  414. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  415. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  416. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  417. package/dist/systems/elevator/elevator-runtime.js +225 -0
  418. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  419. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  420. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  421. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  422. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  423. package/dist/systems/elevator/elevator-service.js +111 -0
  424. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  425. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  426. package/dist/systems/fence/fence-centerline.js +37 -0
  427. package/dist/systems/fence/fence-spline.d.ts +39 -0
  428. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  429. package/dist/systems/fence/fence-spline.js +196 -0
  430. package/dist/systems/fence/fence-spline.test.d.ts +2 -0
  431. package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
  432. package/dist/systems/fence/fence-spline.test.js +136 -0
  433. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  434. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  435. package/dist/systems/stair/stair-footprint.js +178 -0
  436. package/dist/systems/stair/stair-opening-preview.d.ts +2447 -0
  437. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  438. package/dist/systems/stair/stair-opening-preview.js +93 -0
  439. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  440. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  441. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  442. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  443. package/dist/systems/stair/stair-opening-sync.js +155 -147
  444. package/dist/systems/stair/stair-opening-sync.test.js +424 -2
  445. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  446. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  447. package/dist/systems/stair/stair-opening-system.js +104 -0
  448. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  449. package/dist/systems/wall/wall-mitering.js +38 -2
  450. package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
  451. package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
  452. package/dist/systems/wall/wall-mitering.test.js +63 -0
  453. package/dist/systems/wall/wall-move.d.ts +48 -0
  454. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  455. package/dist/systems/wall/wall-move.js +193 -0
  456. package/dist/utils/clone-scene-graph.d.ts +7 -4
  457. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  458. package/dist/utils/clone-scene-graph.js +29 -7
  459. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  460. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  461. package/dist/utils/clone-scene-graph.test.js +62 -0
  462. package/dist/utils/heal-scene-graph.d.ts +18 -0
  463. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  464. package/dist/utils/heal-scene-graph.js +90 -0
  465. package/dist/utils/heal-scene-graph.test.d.ts +2 -0
  466. package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
  467. package/dist/utils/heal-scene-graph.test.js +88 -0
  468. package/dist/validation/validate-build-json.d.ts +44 -0
  469. package/dist/validation/validate-build-json.d.ts.map +1 -0
  470. package/dist/validation/validate-build-json.js +256 -0
  471. package/package.json +11 -4
@@ -0,0 +1,77 @@
1
+ import { nodeRegistry } from './registry';
2
+ const DEFAULT_MAX_DEPTH = 16;
3
+ /**
4
+ * Walks the relations graph from one dirty node and returns the full set of
5
+ * IDs (including the starting one) that should be marked dirty. Pure — does
6
+ * NOT call `scene.markDirty`; callers iterate the result.
7
+ *
8
+ * Phase 1 implements:
9
+ * - `hosts`: marks children whose `type` matches the kind list
10
+ * - `affectsSpatial`: marks neighbors found via `spatialQuery`
11
+ *
12
+ * Phase 3 will add `linkedBy: 'endpoint-match'` for wall corner propagation.
13
+ */
14
+ export function cascadeDirty(startId, ctx) {
15
+ const result = new Set();
16
+ const maxDepth = ctx.maxDepth ?? DEFAULT_MAX_DEPTH;
17
+ walk(startId, ctx, result, 0, maxDepth);
18
+ return result;
19
+ }
20
+ function walk(id, ctx, result, depth, maxDepth) {
21
+ if (result.has(id) || depth > maxDepth)
22
+ return;
23
+ result.add(id);
24
+ const node = ctx.scene.get(id);
25
+ if (!node)
26
+ return;
27
+ const def = nodeRegistry.get(node.type);
28
+ if (!def?.relations)
29
+ return;
30
+ const { hosts, affectsSpatial } = def.relations;
31
+ if (hosts && hosts.length > 0) {
32
+ const childIds = ctx.childQuery ? ctx.childQuery(node) : defaultChildIds(node, ctx.scene);
33
+ for (const childId of childIds) {
34
+ const child = ctx.scene.get(childId);
35
+ if (child && hosts.includes(child.type)) {
36
+ walk(childId, ctx, result, depth + 1, maxDepth);
37
+ }
38
+ }
39
+ }
40
+ if (affectsSpatial && affectsSpatial.length > 0 && ctx.spatialQuery) {
41
+ for (const neighborId of ctx.spatialQuery(node, affectsSpatial)) {
42
+ walk(neighborId, ctx, result, depth + 1, maxDepth);
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Fallback children lookup that reads the node's `children: AnyNodeId[]`
48
+ * field if present. Most parametric nodes carry one; nodes that don't will
49
+ * need a `childQuery` override on the context.
50
+ */
51
+ function defaultChildIds(node, _scene) {
52
+ const maybeChildren = node.children;
53
+ return Array.isArray(maybeChildren) ? maybeChildren : [];
54
+ }
55
+ /**
56
+ * Recursively collects every descendant of a node, plus the node itself.
57
+ * Used by `cascadeDelete: 'descendants'` and by tools that need to delete a
58
+ * subtree atomically. Independent of dirty-marking — pure traversal.
59
+ */
60
+ export function collectDescendants(startId, ctx) {
61
+ const result = new Set();
62
+ const maxDepth = ctx.maxDepth ?? DEFAULT_MAX_DEPTH;
63
+ walkDescendants(startId, ctx, result, 0, maxDepth);
64
+ return result;
65
+ }
66
+ function walkDescendants(id, ctx, result, depth, maxDepth) {
67
+ if (result.has(id) || depth > maxDepth)
68
+ return;
69
+ result.add(id);
70
+ const node = ctx.scene.get(id);
71
+ if (!node)
72
+ return;
73
+ const childIds = ctx.childQuery ? ctx.childQuery(node) : defaultChildIds(node, ctx.scene);
74
+ for (const childId of childIds) {
75
+ walkDescendants(childId, ctx, result, depth + 1, maxDepth);
76
+ }
77
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=relations-resolver.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations-resolver.test.d.ts","sourceRoot":"","sources":["../../src/registry/relations-resolver.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,183 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test';
2
+ import { z } from 'zod';
3
+ import { nodeRegistry, registerNode } from './registry';
4
+ import { cascadeDirty, collectDescendants } from './relations-resolver';
5
+ const id = (s) => s;
6
+ function makeDef(kind, relations, overrides = {}) {
7
+ return {
8
+ kind,
9
+ schemaVersion: 1,
10
+ schema: z.object({ type: z.literal(kind) }),
11
+ category: 'utility',
12
+ defaults: () => ({}),
13
+ capabilities: {},
14
+ relations,
15
+ renderer: { kind: 'parametric', module: async () => ({ default: () => null }) },
16
+ ...overrides,
17
+ };
18
+ }
19
+ function makeNode(kind, idStr, extra = {}) {
20
+ return {
21
+ id: id(idStr),
22
+ type: kind,
23
+ parentId: null,
24
+ visible: true,
25
+ ...extra,
26
+ };
27
+ }
28
+ function makeFakeScene(nodes) {
29
+ return {
30
+ get: ((nid) => nodes[nid]),
31
+ nodes: () => nodes,
32
+ update: () => { },
33
+ upsert: () => id(''),
34
+ delete: () => { },
35
+ restore: () => { },
36
+ restoreAll: () => { },
37
+ markDirty: () => { },
38
+ pauseHistory: () => { },
39
+ resumeHistory: () => { },
40
+ getSubtree: () => null,
41
+ cloneNodesInto: () => null,
42
+ };
43
+ }
44
+ describe('cascadeDirty', () => {
45
+ beforeEach(() => {
46
+ nodeRegistry._reset();
47
+ });
48
+ test('starting node alone when registry is empty', () => {
49
+ const scene = makeFakeScene({ a: makeNode('unknown', 'a') });
50
+ const dirty = cascadeDirty(id('a'), { scene });
51
+ expect(Array.from(dirty)).toEqual([id('a')]);
52
+ });
53
+ test('starting node alone when no relations declared', () => {
54
+ registerNode(makeDef('thing'));
55
+ const scene = makeFakeScene({ a: makeNode('thing', 'a') });
56
+ const dirty = cascadeDirty(id('a'), { scene });
57
+ expect(Array.from(dirty)).toEqual([id('a')]);
58
+ });
59
+ test('hosts cascade marks matching children dirty', () => {
60
+ registerNode(makeDef('wall', { hosts: ['door', 'window'] }));
61
+ registerNode(makeDef('door'));
62
+ registerNode(makeDef('window'));
63
+ registerNode(makeDef('lamp'));
64
+ const wall = makeNode('wall', 'w1', {
65
+ children: [id('d1'), id('w2'), id('l1')],
66
+ });
67
+ const scene = makeFakeScene({
68
+ w1: wall,
69
+ d1: makeNode('door', 'd1', { parentId: id('w1') }),
70
+ w2: makeNode('window', 'w2', { parentId: id('w1') }),
71
+ l1: makeNode('lamp', 'l1', { parentId: id('w1') }), // not in hosts list
72
+ });
73
+ const dirty = cascadeDirty(id('w1'), { scene });
74
+ const ids = Array.from(dirty).sort();
75
+ expect(ids).toEqual([id('d1'), id('w1'), id('w2')]); // l1 excluded
76
+ });
77
+ test('hosts cascade is recursive but bounded by maxDepth', () => {
78
+ registerNode(makeDef('a', { hosts: ['a'] })); // a hosts more a
79
+ const nodes = {};
80
+ for (let i = 0; i < 25; i++) {
81
+ const childId = i < 24 ? id(`a${i + 1}`) : undefined;
82
+ nodes[`a${i}`] = makeNode('a', `a${i}`, {
83
+ children: childId ? [childId] : [],
84
+ });
85
+ }
86
+ const scene = makeFakeScene(nodes);
87
+ const dirty = cascadeDirty(id('a0'), { scene, maxDepth: 5 });
88
+ expect(dirty.size).toBe(6); // a0 + 5 descendants
89
+ });
90
+ test('affectsSpatial cascade uses spatialQuery to find neighbors', () => {
91
+ registerNode(makeDef('wall', { affectsSpatial: ['slab', 'zone'] }));
92
+ registerNode(makeDef('slab'));
93
+ registerNode(makeDef('zone'));
94
+ const scene = makeFakeScene({
95
+ w1: makeNode('wall', 'w1'),
96
+ s1: makeNode('slab', 's1'),
97
+ z1: makeNode('zone', 'z1'),
98
+ unrelated: makeNode('door', 'unrelated'),
99
+ });
100
+ const spatialQuery = (node, kinds) => {
101
+ if (node.id !== id('w1'))
102
+ return [];
103
+ const matches = [];
104
+ if (kinds.includes('slab'))
105
+ matches.push(id('s1'));
106
+ if (kinds.includes('zone'))
107
+ matches.push(id('z1'));
108
+ return matches;
109
+ };
110
+ const dirty = cascadeDirty(id('w1'), { scene, spatialQuery });
111
+ expect(Array.from(dirty).sort()).toEqual([id('s1'), id('w1'), id('z1')]);
112
+ });
113
+ test('affectsSpatial is a no-op when no spatialQuery is provided', () => {
114
+ registerNode(makeDef('wall', { affectsSpatial: ['slab'] }));
115
+ const scene = makeFakeScene({ w1: makeNode('wall', 'w1') });
116
+ const dirty = cascadeDirty(id('w1'), { scene });
117
+ expect(Array.from(dirty)).toEqual([id('w1')]); // spatial branch silently skipped
118
+ });
119
+ test('cycle in hosts cascade does not loop forever', () => {
120
+ registerNode(makeDef('a', { hosts: ['a'] }));
121
+ const nodes = {
122
+ a1: makeNode('a', 'a1', { children: [id('a2')] }),
123
+ a2: makeNode('a', 'a2', { children: [id('a1')] }), // cycle
124
+ };
125
+ const scene = makeFakeScene(nodes);
126
+ const dirty = cascadeDirty(id('a1'), { scene });
127
+ expect(dirty.size).toBe(2);
128
+ expect(dirty.has(id('a1'))).toBe(true);
129
+ expect(dirty.has(id('a2'))).toBe(true);
130
+ });
131
+ test('custom childQuery overrides the default node.children lookup', () => {
132
+ registerNode(makeDef('wall', { hosts: ['door'] }));
133
+ registerNode(makeDef('door'));
134
+ const scene = makeFakeScene({
135
+ w1: makeNode('wall', 'w1'), // no children field
136
+ d1: makeNode('door', 'd1', { parentId: id('w1') }),
137
+ });
138
+ // childQuery iterates the scene to find parentId matches — what you would
139
+ // do for kinds that don't carry an explicit children array.
140
+ const childQuery = (node) => {
141
+ const result = [];
142
+ for (const candidate of [id('d1')]) {
143
+ const c = scene.get(candidate);
144
+ if (c && c.parentId === node.id)
145
+ result.push(c.id);
146
+ }
147
+ return result;
148
+ };
149
+ const dirty = cascadeDirty(id('w1'), { scene, childQuery });
150
+ expect(Array.from(dirty).sort()).toEqual([id('d1'), id('w1')]);
151
+ });
152
+ });
153
+ describe('collectDescendants', () => {
154
+ beforeEach(() => {
155
+ nodeRegistry._reset();
156
+ });
157
+ test('returns just the start when no children', () => {
158
+ const scene = makeFakeScene({ a: makeNode('thing', 'a') });
159
+ const result = collectDescendants(id('a'), { scene });
160
+ expect(Array.from(result)).toEqual([id('a')]);
161
+ });
162
+ test('returns full subtree regardless of relations declarations', () => {
163
+ // No def registered — descendants still found via the children array.
164
+ const scene = makeFakeScene({
165
+ root: makeNode('thing', 'root', { children: [id('c1'), id('c2')] }),
166
+ c1: makeNode('thing', 'c1', { children: [id('g1')] }),
167
+ c2: makeNode('thing', 'c2'),
168
+ g1: makeNode('thing', 'g1'),
169
+ });
170
+ const result = collectDescendants(id('root'), { scene });
171
+ expect(Array.from(result).sort()).toEqual([id('c1'), id('c2'), id('g1'), id('root')]);
172
+ });
173
+ test('respects maxDepth', () => {
174
+ const scene = makeFakeScene({
175
+ a: makeNode('thing', 'a', { children: [id('b')] }),
176
+ b: makeNode('thing', 'b', { children: [id('c')] }),
177
+ c: makeNode('thing', 'c', { children: [id('d')] }),
178
+ d: makeNode('thing', 'd'),
179
+ });
180
+ const result = collectDescendants(id('a'), { scene, maxDepth: 2 });
181
+ expect(Array.from(result).sort()).toEqual([id('a'), id('b'), id('c')]); // d truncated
182
+ });
183
+ });
@@ -0,0 +1,44 @@
1
+ import type { AnyNode, AnyNodeId } from '../schema/types';
2
+ import type { SceneApi } from './types';
3
+ /**
4
+ * Minimal store shape this module depends on.
5
+ *
6
+ * Decoupled from `useScene` directly so the production singleton and tests can
7
+ * share one factory. The full store implements a superset.
8
+ */
9
+ export type SceneStoreLike = {
10
+ getState: () => {
11
+ nodes: Record<AnyNodeId, AnyNode>;
12
+ rootNodeIds: AnyNodeId[];
13
+ dirtyNodes: Set<AnyNodeId>;
14
+ createNode: (node: AnyNode, parentId?: AnyNodeId) => void;
15
+ createNodes?: (ops: {
16
+ node: AnyNode;
17
+ parentId?: AnyNodeId;
18
+ }[]) => void;
19
+ updateNode: (id: AnyNodeId, data: Partial<AnyNode>) => void;
20
+ deleteNode: (id: AnyNodeId) => void;
21
+ markDirty: (id: AnyNodeId) => void;
22
+ };
23
+ temporal: {
24
+ getState: () => {
25
+ pause: () => void;
26
+ resume: () => void;
27
+ };
28
+ };
29
+ };
30
+ /**
31
+ * Creates a {@link SceneApi} backed by a store.
32
+ *
33
+ * Snapshot semantics:
34
+ * - `pauseHistory()` starts a copy-on-write window. The first time `update`,
35
+ * `upsert`, or `delete` touches a node id, the pre-change value is captured.
36
+ * - `restore(id)` and `restoreAll()` apply the captured value back. Either is
37
+ * safe to call only while a pause window is active.
38
+ * - `resumeHistory()` drops the snapshot.
39
+ *
40
+ * Snapshots are lazy and bounded by the number of nodes touched during the
41
+ * pause window — never an upfront clone of the entire scene.
42
+ */
43
+ export declare function createSceneApi(store: SceneStoreLike): SceneApi;
44
+ //# sourceMappingURL=scene-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-api.d.ts","sourceRoot":"","sources":["../../src/registry/scene-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAOzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM;QACd,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QACjC,WAAW,EAAE,SAAS,EAAE,CAAA;QACxB,UAAU,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;QAC1B,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,IAAI,CAAA;QACzD,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;SAAE,EAAE,KAAK,IAAI,CAAA;QACtE,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;QAC3D,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;QACnC,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;KACnC,CAAA;IACD,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM;YAAE,KAAK,EAAE,MAAM,IAAI,CAAC;YAAC,MAAM,EAAE,MAAM,IAAI,CAAA;SAAE,CAAA;KAC1D,CAAA;CACF,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,QAAQ,CAgG9D"}
@@ -0,0 +1,111 @@
1
+ import { pauseSceneHistory, resumeSceneHistory } from '../store/history-control';
2
+ import { collectSubtree, cloneNodesInto as runCloneNodesInto, } from './subtree';
3
+ /**
4
+ * Creates a {@link SceneApi} backed by a store.
5
+ *
6
+ * Snapshot semantics:
7
+ * - `pauseHistory()` starts a copy-on-write window. The first time `update`,
8
+ * `upsert`, or `delete` touches a node id, the pre-change value is captured.
9
+ * - `restore(id)` and `restoreAll()` apply the captured value back. Either is
10
+ * safe to call only while a pause window is active.
11
+ * - `resumeHistory()` drops the snapshot.
12
+ *
13
+ * Snapshots are lazy and bounded by the number of nodes touched during the
14
+ * pause window — never an upfront clone of the entire scene.
15
+ */
16
+ export function createSceneApi(store) {
17
+ let snapshot = null;
18
+ function captureIfNeeded(id) {
19
+ if (!snapshot || snapshot.has(id))
20
+ return;
21
+ const existing = store.getState().nodes[id];
22
+ snapshot.set(id, existing ?? null);
23
+ }
24
+ return {
25
+ get(id) {
26
+ return store.getState().nodes[id];
27
+ },
28
+ nodes() {
29
+ return store.getState().nodes;
30
+ },
31
+ update(id, patch) {
32
+ captureIfNeeded(id);
33
+ store.getState().updateNode(id, patch);
34
+ },
35
+ upsert(node, parentId) {
36
+ captureIfNeeded(node.id);
37
+ store.getState().createNode(node, parentId);
38
+ return node.id;
39
+ },
40
+ delete(id) {
41
+ captureIfNeeded(id);
42
+ store.getState().deleteNode(id);
43
+ },
44
+ restore(id) {
45
+ if (!snapshot)
46
+ return;
47
+ const original = snapshot.get(id);
48
+ if (original === undefined)
49
+ return;
50
+ const current = store.getState().nodes[id];
51
+ if (original === null) {
52
+ if (current)
53
+ store.getState().deleteNode(id);
54
+ }
55
+ else if (!current) {
56
+ store.getState().createNode(original);
57
+ }
58
+ else {
59
+ store.getState().updateNode(id, original);
60
+ }
61
+ },
62
+ restoreAll() {
63
+ if (!snapshot)
64
+ return;
65
+ for (const id of snapshot.keys()) {
66
+ this.restore(id);
67
+ }
68
+ },
69
+ markDirty(id) {
70
+ store.getState().markDirty(id);
71
+ },
72
+ pauseHistory() {
73
+ pauseSceneHistory(store);
74
+ if (!snapshot)
75
+ snapshot = new Map();
76
+ },
77
+ resumeHistory() {
78
+ resumeSceneHistory(store);
79
+ snapshot = null;
80
+ },
81
+ getSubtree(rootId) {
82
+ return collectSubtree(store.getState().nodes, rootId);
83
+ },
84
+ cloneNodesInto(nodes, opts) {
85
+ const { rootId, nodes: cloned } = runCloneNodesInto(nodes, opts);
86
+ const root = cloned[0];
87
+ if (!root)
88
+ return null;
89
+ const state = store.getState();
90
+ const ops = [];
91
+ for (let i = 0; i < cloned.length; i += 1) {
92
+ const node = cloned[i];
93
+ if (i === 0) {
94
+ ops.push(opts.parentId ? { node, parentId: opts.parentId } : { node });
95
+ }
96
+ else {
97
+ ops.push({ node });
98
+ }
99
+ }
100
+ const batch = state.createNodes;
101
+ if (batch) {
102
+ batch(ops);
103
+ }
104
+ else {
105
+ for (const op of ops)
106
+ state.createNode(op.node, op.parentId);
107
+ }
108
+ return rootId;
109
+ },
110
+ };
111
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scene-api.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-api.test.d.ts","sourceRoot":"","sources":["../../src/registry/scene-api.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,183 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test';
2
+ import { resetSceneHistoryPauseDepth } from '../store/history-control';
3
+ import { createSceneApi } from './scene-api';
4
+ function makeFakeStore(initial = {}) {
5
+ const state = {
6
+ nodes: { ...initial },
7
+ rootNodeIds: [],
8
+ dirtyNodes: new Set(),
9
+ createNode(node) {
10
+ state.nodes[node.id] = node;
11
+ },
12
+ updateNode(id, data) {
13
+ const existing = state.nodes[id];
14
+ if (existing)
15
+ state.nodes[id] = { ...existing, ...data };
16
+ },
17
+ deleteNode(id) {
18
+ delete state.nodes[id];
19
+ },
20
+ markDirty(id) {
21
+ state.dirtyNodes.add(id);
22
+ },
23
+ };
24
+ let paused = 0;
25
+ const temporal = {
26
+ getState: () => ({
27
+ pause: () => {
28
+ paused += 1;
29
+ },
30
+ resume: () => {
31
+ paused -= 1;
32
+ },
33
+ }),
34
+ };
35
+ const store = {
36
+ getState: () => state,
37
+ temporal,
38
+ _state: state,
39
+ _pausedCount: () => paused,
40
+ };
41
+ return store;
42
+ }
43
+ function makeNode(id, extra = {}) {
44
+ return { id, type: 'site', parentId: null, visible: true, ...extra };
45
+ }
46
+ // Tests use short string IDs ("a", "b") for readability. The store's
47
+ // AnyNodeId is a branded template-literal type — cast at the boundary.
48
+ const id = (s) => s;
49
+ const nodes = (store) => store._state.nodes;
50
+ describe('SceneApi', () => {
51
+ beforeEach(() => {
52
+ resetSceneHistoryPauseDepth();
53
+ });
54
+ test('get reads node from store', () => {
55
+ const store = makeFakeStore({ a: makeNode('a') });
56
+ const api = createSceneApi(store);
57
+ expect(api.get(id('a'))).toEqual(makeNode('a'));
58
+ expect(api.get(id('missing'))).toBeUndefined();
59
+ });
60
+ test('update applies patch via store.updateNode', () => {
61
+ const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
62
+ const api = createSceneApi(store);
63
+ api.update(id('a'), { visible: false });
64
+ expect(nodes(store)['a']).toMatchObject({ visible: false });
65
+ });
66
+ test('upsert calls createNode and returns the id', () => {
67
+ const store = makeFakeStore();
68
+ const api = createSceneApi(store);
69
+ const returnedId = api.upsert(makeNode('a'));
70
+ expect(returnedId).toBe(id('a'));
71
+ expect(nodes(store)['a']).toBeDefined();
72
+ });
73
+ test('delete removes node from store', () => {
74
+ const store = makeFakeStore({ a: makeNode('a') });
75
+ const api = createSceneApi(store);
76
+ api.delete(id('a'));
77
+ expect(nodes(store)['a']).toBeUndefined();
78
+ });
79
+ test('markDirty forwards to store', () => {
80
+ const store = makeFakeStore({ a: makeNode('a') });
81
+ const api = createSceneApi(store);
82
+ api.markDirty(id('a'));
83
+ expect(store._state.dirtyNodes.has(id('a'))).toBe(true);
84
+ });
85
+ test('pauseHistory and resumeHistory bracket store.temporal pause/resume', () => {
86
+ const store = makeFakeStore();
87
+ const api = createSceneApi(store);
88
+ expect(store._pausedCount()).toBe(0);
89
+ api.pauseHistory();
90
+ expect(store._pausedCount()).toBe(1);
91
+ api.resumeHistory();
92
+ expect(store._pausedCount()).toBe(0);
93
+ });
94
+ test('nested pause/resume use a depth counter (single pause call to temporal)', () => {
95
+ const store = makeFakeStore();
96
+ const api = createSceneApi(store);
97
+ api.pauseHistory();
98
+ api.pauseHistory();
99
+ expect(store._pausedCount()).toBe(1); // only one actual pause
100
+ api.resumeHistory();
101
+ expect(store._pausedCount()).toBe(1); // still paused — inner resume
102
+ api.resumeHistory();
103
+ expect(store._pausedCount()).toBe(0);
104
+ });
105
+ });
106
+ describe('SceneApi snapshot / restore', () => {
107
+ beforeEach(() => {
108
+ resetSceneHistoryPauseDepth();
109
+ });
110
+ test('restore returns a touched node to its pre-pause state', () => {
111
+ const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
112
+ const api = createSceneApi(store);
113
+ api.pauseHistory();
114
+ api.update(id('a'), { visible: false });
115
+ expect(nodes(store)['a']).toMatchObject({ visible: false });
116
+ api.restore(id('a'));
117
+ expect(nodes(store)['a']).toMatchObject({ visible: true });
118
+ api.resumeHistory();
119
+ });
120
+ test('restore on a node never touched is a no-op', () => {
121
+ const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
122
+ const api = createSceneApi(store);
123
+ api.pauseHistory();
124
+ api.restore(id('a'));
125
+ expect(nodes(store)['a']).toMatchObject({ visible: true });
126
+ api.resumeHistory();
127
+ });
128
+ test('restoreAll reverts every touched node', () => {
129
+ const store = makeFakeStore({
130
+ a: makeNode('a', { visible: true }),
131
+ b: makeNode('b', { visible: true }),
132
+ });
133
+ const api = createSceneApi(store);
134
+ api.pauseHistory();
135
+ api.update(id('a'), { visible: false });
136
+ api.update(id('b'), { visible: false });
137
+ api.restoreAll();
138
+ expect(nodes(store)['a']).toMatchObject({ visible: true });
139
+ expect(nodes(store)['b']).toMatchObject({ visible: true });
140
+ api.resumeHistory();
141
+ });
142
+ test('restore re-creates a node that was deleted mid-pause', () => {
143
+ const original = makeNode('a', { visible: true });
144
+ const store = makeFakeStore({ a: original });
145
+ const api = createSceneApi(store);
146
+ api.pauseHistory();
147
+ api.delete(id('a'));
148
+ expect(nodes(store)['a']).toBeUndefined();
149
+ api.restore(id('a'));
150
+ expect(nodes(store)['a']).toEqual(original);
151
+ api.resumeHistory();
152
+ });
153
+ test('restore deletes a node that was upserted mid-pause', () => {
154
+ const store = makeFakeStore();
155
+ const api = createSceneApi(store);
156
+ api.pauseHistory();
157
+ api.upsert(makeNode('a'));
158
+ expect(nodes(store)['a']).toBeDefined();
159
+ api.restore(id('a'));
160
+ expect(nodes(store)['a']).toBeUndefined();
161
+ api.resumeHistory();
162
+ });
163
+ test('snapshot is dropped on resumeHistory; restore after resume is a no-op', () => {
164
+ const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
165
+ const api = createSceneApi(store);
166
+ api.pauseHistory();
167
+ api.update(id('a'), { visible: false });
168
+ api.resumeHistory();
169
+ api.restore(id('a')); // snapshot gone — no effect
170
+ expect(nodes(store)['a']).toMatchObject({ visible: false });
171
+ });
172
+ test('only the first mutation in a pause window captures the original', () => {
173
+ const store = makeFakeStore({ a: makeNode('a', { visible: true }) });
174
+ const api = createSceneApi(store);
175
+ api.pauseHistory();
176
+ api.update(id('a'), { visible: false });
177
+ api.update(id('a'), { visible: true }); // second update — must not overwrite snapshot
178
+ api.update(id('a'), { visible: false });
179
+ api.restore(id('a'));
180
+ expect(nodes(store)['a']).toMatchObject({ visible: true }); // the *first* pre-pause value
181
+ api.resumeHistory();
182
+ });
183
+ });