@pascal-app/core 0.9.1 → 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 (321) hide show
  1. package/dist/events/bus.d.ts +30 -2
  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 +8 -1
  7. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  8. package/dist/hooks/scene-registry/scene-registry.js +27 -1
  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.test.js +93 -0
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +69 -12
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  15. package/dist/hooks/spatial-grid/spatial-grid-manager.js +476 -195
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.js +19 -1
  18. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts +2 -0
  19. package/dist/hooks/spatial-grid/wall-slab-overlap.test.d.ts.map +1 -0
  20. package/dist/hooks/spatial-grid/wall-slab-overlap.test.js +363 -0
  21. package/dist/index.d.ts +16 -6
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +15 -5
  24. package/dist/lib/level-name.d.ts +4 -0
  25. package/dist/lib/level-name.d.ts.map +1 -0
  26. package/dist/lib/level-name.js +10 -0
  27. package/dist/lib/measurement-geometry.d.ts +19 -0
  28. package/dist/lib/measurement-geometry.d.ts.map +1 -0
  29. package/dist/lib/measurement-geometry.js +232 -0
  30. package/dist/lib/measurement-geometry.test.d.ts +2 -0
  31. package/dist/lib/measurement-geometry.test.d.ts.map +1 -0
  32. package/dist/lib/measurement-geometry.test.js +102 -0
  33. package/dist/lib/polygon-geometry.d.ts +0 -1
  34. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  35. package/dist/lib/polygon-geometry.js +0 -18
  36. package/dist/lib/selection-proxy.d.ts +4 -0
  37. package/dist/lib/selection-proxy.d.ts.map +1 -0
  38. package/dist/lib/selection-proxy.js +12 -0
  39. package/dist/lib/slab-polygon.d.ts +31 -2
  40. package/dist/lib/slab-polygon.d.ts.map +1 -1
  41. package/dist/lib/slab-polygon.js +525 -31
  42. package/dist/lib/slab-polygon.test.d.ts +2 -0
  43. package/dist/lib/slab-polygon.test.d.ts.map +1 -0
  44. package/dist/lib/slab-polygon.test.js +635 -0
  45. package/dist/lib/slots.d.ts +14 -0
  46. package/dist/lib/slots.d.ts.map +1 -0
  47. package/dist/lib/slots.js +26 -0
  48. package/dist/lib/space-detection.d.ts +25 -2
  49. package/dist/lib/space-detection.d.ts.map +1 -1
  50. package/dist/lib/space-detection.js +414 -118
  51. package/dist/lib/space-detection.test.js +301 -2
  52. package/dist/lib/wall-distance.d.ts +60 -0
  53. package/dist/lib/wall-distance.d.ts.map +1 -0
  54. package/dist/lib/wall-distance.js +85 -0
  55. package/dist/lib/zone-quantities.d.ts +21 -0
  56. package/dist/lib/zone-quantities.d.ts.map +1 -0
  57. package/dist/lib/zone-quantities.js +183 -0
  58. package/dist/lib/zone-quantities.test.d.ts +2 -0
  59. package/dist/lib/zone-quantities.test.d.ts.map +1 -0
  60. package/dist/lib/zone-quantities.test.js +82 -0
  61. package/dist/material-library.d.ts +20 -1
  62. package/dist/material-library.d.ts.map +1 -1
  63. package/dist/material-library.js +2181 -441
  64. package/dist/registry/handles.d.ts +75 -2
  65. package/dist/registry/handles.d.ts.map +1 -1
  66. package/dist/registry/index.d.ts +3 -3
  67. package/dist/registry/index.d.ts.map +1 -1
  68. package/dist/registry/index.js +1 -1
  69. package/dist/registry/registry.d.ts +46 -3
  70. package/dist/registry/registry.d.ts.map +1 -1
  71. package/dist/registry/registry.js +82 -1
  72. package/dist/registry/registry.test.js +14 -1
  73. package/dist/registry/subtree.d.ts.map +1 -1
  74. package/dist/registry/subtree.js +4 -0
  75. package/dist/registry/subtree.test.js +30 -0
  76. package/dist/registry/types.d.ts +717 -38
  77. package/dist/registry/types.d.ts.map +1 -1
  78. package/dist/schema/index.d.ts +21 -5
  79. package/dist/schema/index.d.ts.map +1 -1
  80. package/dist/schema/index.js +17 -3
  81. package/dist/schema/material.d.ts +3 -2
  82. package/dist/schema/material.d.ts.map +1 -1
  83. package/dist/schema/material.js +7 -5
  84. package/dist/schema/material.test.d.ts +2 -0
  85. package/dist/schema/material.test.d.ts.map +1 -0
  86. package/dist/schema/material.test.js +43 -0
  87. package/dist/schema/nodes/box-vent.d.ts +2 -2
  88. package/dist/schema/nodes/cabinet.d.ts +534 -0
  89. package/dist/schema/nodes/cabinet.d.ts.map +1 -0
  90. package/dist/schema/nodes/cabinet.js +137 -0
  91. package/dist/schema/nodes/ceiling.d.ts +3 -2
  92. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  93. package/dist/schema/nodes/ceiling.js +4 -0
  94. package/dist/schema/nodes/chimney.d.ts +4 -4
  95. package/dist/schema/nodes/column.d.ts +3 -2
  96. package/dist/schema/nodes/column.d.ts.map +1 -1
  97. package/dist/schema/nodes/column.js +3 -0
  98. package/dist/schema/nodes/cupola.d.ts +2 -2
  99. package/dist/schema/nodes/door.d.ts +13 -5
  100. package/dist/schema/nodes/door.d.ts.map +1 -1
  101. package/dist/schema/nodes/door.js +12 -0
  102. package/dist/schema/nodes/dormer.d.ts +8 -8
  103. package/dist/schema/nodes/dormer.js +1 -1
  104. package/dist/schema/nodes/downspout.d.ts +2 -2
  105. package/dist/schema/nodes/duct-fitting.d.ts +91 -0
  106. package/dist/schema/nodes/duct-fitting.d.ts.map +1 -0
  107. package/dist/schema/nodes/duct-fitting.js +92 -0
  108. package/dist/schema/nodes/duct-segment.d.ts +61 -0
  109. package/dist/schema/nodes/duct-segment.d.ts.map +1 -0
  110. package/dist/schema/nodes/duct-segment.js +73 -0
  111. package/dist/schema/nodes/duct-terminal.d.ts +57 -0
  112. package/dist/schema/nodes/duct-terminal.d.ts.map +1 -0
  113. package/dist/schema/nodes/duct-terminal.js +51 -0
  114. package/dist/schema/nodes/elevator.d.ts +3 -2
  115. package/dist/schema/nodes/elevator.d.ts.map +1 -1
  116. package/dist/schema/nodes/elevator.js +3 -0
  117. package/dist/schema/nodes/eyebrow-vent.d.ts +2 -2
  118. package/dist/schema/nodes/fence.d.ts +18 -2
  119. package/dist/schema/nodes/fence.d.ts.map +1 -1
  120. package/dist/schema/nodes/fence.js +25 -2
  121. package/dist/schema/nodes/gutter.d.ts +2 -2
  122. package/dist/schema/nodes/hvac-equipment.d.ts +63 -0
  123. package/dist/schema/nodes/hvac-equipment.d.ts.map +1 -0
  124. package/dist/schema/nodes/hvac-equipment.js +55 -0
  125. package/dist/schema/nodes/item.d.ts +8 -0
  126. package/dist/schema/nodes/item.d.ts.map +1 -1
  127. package/dist/schema/nodes/item.js +11 -0
  128. package/dist/schema/nodes/level.d.ts +1 -1
  129. package/dist/schema/nodes/level.d.ts.map +1 -1
  130. package/dist/schema/nodes/level.js +2 -0
  131. package/dist/schema/nodes/lineset.d.ts +42 -0
  132. package/dist/schema/nodes/lineset.d.ts.map +1 -0
  133. package/dist/schema/nodes/lineset.js +38 -0
  134. package/dist/schema/nodes/liquid-line.d.ts +34 -0
  135. package/dist/schema/nodes/liquid-line.d.ts.map +1 -0
  136. package/dist/schema/nodes/liquid-line.js +24 -0
  137. package/dist/schema/nodes/measurement.d.ts +306 -0
  138. package/dist/schema/nodes/measurement.d.ts.map +1 -0
  139. package/dist/schema/nodes/measurement.js +87 -0
  140. package/dist/schema/nodes/measurement.test.d.ts +2 -0
  141. package/dist/schema/nodes/measurement.test.d.ts.map +1 -0
  142. package/dist/schema/nodes/measurement.test.js +139 -0
  143. package/dist/schema/nodes/pipe-fitting.d.ts +56 -0
  144. package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -0
  145. package/dist/schema/nodes/pipe-fitting.js +45 -0
  146. package/dist/schema/nodes/pipe-segment.d.ts +48 -0
  147. package/dist/schema/nodes/pipe-segment.d.ts.map +1 -0
  148. package/dist/schema/nodes/pipe-segment.js +36 -0
  149. package/dist/schema/nodes/pipe-trap.d.ts +43 -0
  150. package/dist/schema/nodes/pipe-trap.d.ts.map +1 -0
  151. package/dist/schema/nodes/pipe-trap.js +36 -0
  152. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts +2 -0
  153. package/dist/schema/nodes/ridge-vent-defaults.test.d.ts.map +1 -0
  154. package/dist/schema/nodes/ridge-vent-defaults.test.js +205 -0
  155. package/dist/schema/nodes/ridge-vent.d.ts +18 -3
  156. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
  157. package/dist/schema/nodes/ridge-vent.js +221 -3
  158. package/dist/schema/nodes/roof-segment-shape.d.ts +90 -0
  159. package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -0
  160. package/dist/schema/nodes/roof-segment-shape.js +318 -0
  161. package/dist/schema/nodes/roof-segment-shape.test.d.ts +2 -0
  162. package/dist/schema/nodes/roof-segment-shape.test.d.ts.map +1 -0
  163. package/dist/schema/nodes/roof-segment-shape.test.js +175 -0
  164. package/dist/schema/nodes/roof-segment-surface.test.d.ts +2 -0
  165. package/dist/schema/nodes/roof-segment-surface.test.d.ts.map +1 -0
  166. package/dist/schema/nodes/roof-segment-surface.test.js +48 -0
  167. package/dist/schema/nodes/roof-segment-trim.test.d.ts +2 -0
  168. package/dist/schema/nodes/roof-segment-trim.test.d.ts.map +1 -0
  169. package/dist/schema/nodes/roof-segment-trim.test.js +107 -0
  170. package/dist/schema/nodes/roof-segment-walls.d.ts +101 -0
  171. package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -0
  172. package/dist/schema/nodes/roof-segment-walls.js +330 -0
  173. package/dist/schema/nodes/roof-segment-walls.test.d.ts +2 -0
  174. package/dist/schema/nodes/roof-segment-walls.test.d.ts.map +1 -0
  175. package/dist/schema/nodes/roof-segment-walls.test.js +92 -0
  176. package/dist/schema/nodes/roof-segment.d.ts +84 -8
  177. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  178. package/dist/schema/nodes/roof-segment.js +290 -5
  179. package/dist/schema/nodes/roof.d.ts +8 -8
  180. package/dist/schema/nodes/shelf.d.ts +3 -2
  181. package/dist/schema/nodes/shelf.d.ts.map +1 -1
  182. package/dist/schema/nodes/shelf.js +6 -0
  183. package/dist/schema/nodes/skylight.d.ts +10 -10
  184. package/dist/schema/nodes/slab.d.ts +3 -2
  185. package/dist/schema/nodes/slab.d.ts.map +1 -1
  186. package/dist/schema/nodes/slab.js +4 -0
  187. package/dist/schema/nodes/solar-panel.d.ts +4 -4
  188. package/dist/schema/nodes/stair-segment.d.ts +4 -4
  189. package/dist/schema/nodes/stair.d.ts +13 -12
  190. package/dist/schema/nodes/stair.d.ts.map +1 -1
  191. package/dist/schema/nodes/stair.js +3 -0
  192. package/dist/schema/nodes/turbine-vent.d.ts +2 -2
  193. package/dist/schema/nodes/wall.d.ts +245 -6
  194. package/dist/schema/nodes/wall.d.ts.map +1 -1
  195. package/dist/schema/nodes/wall.js +257 -0
  196. package/dist/schema/nodes/wall.test.d.ts +2 -0
  197. package/dist/schema/nodes/wall.test.d.ts.map +1 -0
  198. package/dist/schema/nodes/wall.test.js +208 -0
  199. package/dist/schema/nodes/window.d.ts +11 -3
  200. package/dist/schema/nodes/window.d.ts.map +1 -1
  201. package/dist/schema/nodes/window.js +12 -0
  202. package/dist/schema/nodes/zone.d.ts +2 -0
  203. package/dist/schema/nodes/zone.d.ts.map +1 -1
  204. package/dist/schema/nodes/zone.js +6 -0
  205. package/dist/schema/scene-material.d.ts +41 -0
  206. package/dist/schema/scene-material.d.ts.map +1 -0
  207. package/dist/schema/scene-material.js +9 -0
  208. package/dist/schema/types.d.ts +1089 -94
  209. package/dist/schema/types.d.ts.map +1 -1
  210. package/dist/schema/types.js +23 -0
  211. package/dist/services/alignment-anchors.d.ts.map +1 -1
  212. package/dist/services/alignment-anchors.js +30 -1
  213. package/dist/services/alignment.d.ts +5 -0
  214. package/dist/services/alignment.d.ts.map +1 -1
  215. package/dist/services/alignment.js +2 -0
  216. package/dist/services/hosting.d.ts +8 -0
  217. package/dist/services/hosting.d.ts.map +1 -1
  218. package/dist/services/hosting.js +13 -4
  219. package/dist/services/hosting.test.js +32 -1
  220. package/dist/services/index.d.ts +8 -2
  221. package/dist/services/index.d.ts.map +1 -1
  222. package/dist/services/index.js +8 -2
  223. package/dist/services/level-height.d.ts +28 -0
  224. package/dist/services/level-height.d.ts.map +1 -0
  225. package/dist/services/level-height.js +62 -0
  226. package/dist/services/opening-guides.d.ts +141 -0
  227. package/dist/services/opening-guides.d.ts.map +1 -0
  228. package/dist/services/opening-guides.js +267 -0
  229. package/dist/services/opening-guides.test.d.ts +2 -0
  230. package/dist/services/opening-guides.test.d.ts.map +1 -0
  231. package/dist/services/opening-guides.test.js +208 -0
  232. package/dist/services/port-connectivity.d.ts +115 -0
  233. package/dist/services/port-connectivity.d.ts.map +1 -0
  234. package/dist/services/port-connectivity.js +300 -0
  235. package/dist/services/port-connectivity.test.d.ts +2 -0
  236. package/dist/services/port-connectivity.test.d.ts.map +1 -0
  237. package/dist/services/port-connectivity.test.js +297 -0
  238. package/dist/services/riser-diagram.d.ts +40 -0
  239. package/dist/services/riser-diagram.d.ts.map +1 -0
  240. package/dist/services/riser-diagram.js +110 -0
  241. package/dist/services/riser-diagram.test.d.ts +2 -0
  242. package/dist/services/riser-diagram.test.d.ts.map +1 -0
  243. package/dist/services/riser-diagram.test.js +66 -0
  244. package/dist/services/snap.d.ts +9 -1
  245. package/dist/services/snap.d.ts.map +1 -1
  246. package/dist/services/snap.js +21 -1
  247. package/dist/services/snap.test.js +43 -1
  248. package/dist/services/system-graph.d.ts +30 -0
  249. package/dist/services/system-graph.d.ts.map +1 -0
  250. package/dist/services/system-graph.js +164 -0
  251. package/dist/services/system-graph.test.d.ts +2 -0
  252. package/dist/services/system-graph.test.d.ts.map +1 -0
  253. package/dist/services/system-graph.test.js +138 -0
  254. package/dist/services/validate-dwv.d.ts +32 -0
  255. package/dist/services/validate-dwv.d.ts.map +1 -0
  256. package/dist/services/validate-dwv.js +118 -0
  257. package/dist/services/validate-dwv.test.d.ts +2 -0
  258. package/dist/services/validate-dwv.test.d.ts.map +1 -0
  259. package/dist/services/validate-dwv.test.js +109 -0
  260. package/dist/store/actions/node-actions.d.ts.map +1 -1
  261. package/dist/store/actions/node-actions.js +94 -1
  262. package/dist/store/actions/ridge-vent-update.test.d.ts +2 -0
  263. package/dist/store/actions/ridge-vent-update.test.d.ts.map +1 -0
  264. package/dist/store/actions/ridge-vent-update.test.js +264 -0
  265. package/dist/store/history-control.d.ts +11 -0
  266. package/dist/store/history-control.d.ts.map +1 -1
  267. package/dist/store/history-control.js +33 -0
  268. package/dist/store/use-scene-dirty-tracking.test.d.ts +2 -0
  269. package/dist/store/use-scene-dirty-tracking.test.d.ts.map +1 -0
  270. package/dist/store/use-scene-dirty-tracking.test.js +68 -0
  271. package/dist/store/use-scene-measurement-roundtrip.test.d.ts +2 -0
  272. package/dist/store/use-scene-measurement-roundtrip.test.d.ts.map +1 -0
  273. package/dist/store/use-scene-measurement-roundtrip.test.js +97 -0
  274. package/dist/store/use-scene-plugins.test.d.ts +2 -0
  275. package/dist/store/use-scene-plugins.test.d.ts.map +1 -0
  276. package/dist/store/use-scene-plugins.test.js +51 -0
  277. package/dist/store/use-scene-wall-slot-migration.test.d.ts +2 -0
  278. package/dist/store/use-scene-wall-slot-migration.test.d.ts.map +1 -0
  279. package/dist/store/use-scene-wall-slot-migration.test.js +210 -0
  280. package/dist/store/use-scene-window-migration.test.d.ts +2 -0
  281. package/dist/store/use-scene-window-migration.test.d.ts.map +1 -0
  282. package/dist/store/use-scene-window-migration.test.js +85 -0
  283. package/dist/store/use-scene.d.ts +17 -2
  284. package/dist/store/use-scene.d.ts.map +1 -1
  285. package/dist/store/use-scene.js +358 -51
  286. package/dist/systems/elevator/elevator-service.js +1 -1
  287. package/dist/systems/fence/fence-centerline.d.ts +12 -0
  288. package/dist/systems/fence/fence-centerline.d.ts.map +1 -0
  289. package/dist/systems/fence/fence-centerline.js +37 -0
  290. package/dist/systems/fence/fence-spline.d.ts +39 -0
  291. package/dist/systems/fence/fence-spline.d.ts.map +1 -0
  292. package/dist/systems/fence/fence-spline.js +196 -0
  293. package/dist/systems/fence/fence-spline.test.d.ts +2 -0
  294. package/dist/systems/fence/fence-spline.test.d.ts.map +1 -0
  295. package/dist/systems/fence/fence-spline.test.js +136 -0
  296. package/dist/systems/stair/stair-footprint.d.ts.map +1 -1
  297. package/dist/systems/stair/stair-footprint.js +21 -11
  298. package/dist/systems/stair/stair-opening-preview.d.ts +1139 -469
  299. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
  300. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  301. package/dist/systems/stair/stair-opening-sync.js +1 -18
  302. package/dist/systems/stair/stair-opening-sync.test.js +39 -0
  303. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  304. package/dist/systems/wall/wall-mitering.js +38 -2
  305. package/dist/systems/wall/wall-mitering.test.d.ts +2 -0
  306. package/dist/systems/wall/wall-mitering.test.d.ts.map +1 -0
  307. package/dist/systems/wall/wall-mitering.test.js +63 -0
  308. package/dist/utils/clone-scene-graph.d.ts +1 -0
  309. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  310. package/dist/utils/clone-scene-graph.js +23 -3
  311. package/dist/utils/clone-scene-graph.test.js +3 -0
  312. package/dist/utils/heal-scene-graph.d.ts +18 -0
  313. package/dist/utils/heal-scene-graph.d.ts.map +1 -0
  314. package/dist/utils/heal-scene-graph.js +90 -0
  315. package/dist/utils/heal-scene-graph.test.d.ts +2 -0
  316. package/dist/utils/heal-scene-graph.test.d.ts.map +1 -0
  317. package/dist/utils/heal-scene-graph.test.js +88 -0
  318. package/dist/validation/validate-build-json.d.ts +1 -0
  319. package/dist/validation/validate-build-json.d.ts.map +1 -1
  320. package/dist/validation/validate-build-json.js +31 -2
  321. package/package.json +3 -3
@@ -0,0 +1,264 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test';
2
+ import { createDefaultRidgeVentsForSegment, RidgeVentNode } from '../../schema/nodes/ridge-vent';
3
+ import { RoofNode } from '../../schema/nodes/roof';
4
+ import { RoofSegmentNode } from '../../schema/nodes/roof-segment';
5
+ import useScene from '../use-scene';
6
+ globalThis.requestAnimationFrame ??= ((cb) => {
7
+ cb(0);
8
+ return 0;
9
+ });
10
+ globalThis.cancelAnimationFrame ??=
11
+ () => { };
12
+ describe('roof segment default ridge vents', () => {
13
+ beforeEach(() => {
14
+ useScene.setState({
15
+ nodes: {},
16
+ rootNodeIds: [],
17
+ dirtyNodes: new Set(),
18
+ collections: {},
19
+ materials: {},
20
+ readOnly: false,
21
+ });
22
+ });
23
+ test('regenerates default ridge vents when the host ridge geometry changes', () => {
24
+ const roof = RoofNode.parse({ id: 'roof_test', children: [] });
25
+ const segment = RoofSegmentNode.parse({
26
+ id: 'rseg_test',
27
+ parentId: roof.id,
28
+ roofType: 'gable',
29
+ width: 8,
30
+ depth: 6,
31
+ });
32
+ const defaults = createDefaultRidgeVentsForSegment(segment);
33
+ const custom = RidgeVentNode.parse({
34
+ id: 'rvent_custom',
35
+ parentId: segment.id,
36
+ roofSegmentId: segment.id,
37
+ name: 'Custom Ridge Vent',
38
+ position: [0, 0.2, 0],
39
+ length: 1.25,
40
+ materialPreset: 'preset-custom',
41
+ });
42
+ useScene.getState().setScene({
43
+ [roof.id]: { ...roof, children: [segment.id] },
44
+ [segment.id]: {
45
+ ...segment,
46
+ children: [...defaults.map((vent) => vent.id), custom.id],
47
+ },
48
+ ...Object.fromEntries(defaults.map((vent) => [
49
+ vent.id,
50
+ { ...vent, parentId: segment.id, roofSegmentId: segment.id },
51
+ ])),
52
+ [custom.id]: custom,
53
+ }, [roof.id]);
54
+ const oldDefaultIds = defaults.map((vent) => vent.id);
55
+ useScene.getState().updateNode(segment.id, { width: 12 });
56
+ const nextSegment = useScene.getState().nodes[segment.id];
57
+ const nextChildren = nextSegment?.children ?? [];
58
+ const nextDefaultIds = nextChildren.filter((id) => id !== custom.id);
59
+ expect(nextChildren).toContain(custom.id);
60
+ expect(useScene.getState().nodes[custom.id]).toMatchObject({
61
+ length: 1.25,
62
+ materialPreset: 'preset-custom',
63
+ });
64
+ for (const oldId of oldDefaultIds) {
65
+ expect(useScene.getState().nodes[oldId]).toBeUndefined();
66
+ }
67
+ expect(nextDefaultIds).toHaveLength(defaults.length);
68
+ expect(nextDefaultIds.some((id) => {
69
+ const node = useScene.getState().nodes[id];
70
+ return node?.type === 'ridge-vent' && node.length > (defaults[0]?.length ?? 0);
71
+ })).toBe(true);
72
+ });
73
+ test('does not regenerate default ridge vents when only trim changes', () => {
74
+ const roof = RoofNode.parse({ id: 'roof_test', children: [] });
75
+ const segment = RoofSegmentNode.parse({
76
+ id: 'rseg_test',
77
+ parentId: roof.id,
78
+ roofType: 'gable',
79
+ width: 8,
80
+ depth: 6,
81
+ });
82
+ const defaults = createDefaultRidgeVentsForSegment(segment);
83
+ useScene.getState().setScene({
84
+ [roof.id]: { ...roof, children: [segment.id] },
85
+ [segment.id]: {
86
+ ...segment,
87
+ children: defaults.map((vent) => vent.id),
88
+ },
89
+ ...Object.fromEntries(defaults.map((vent) => [
90
+ vent.id,
91
+ { ...vent, parentId: segment.id, roofSegmentId: segment.id },
92
+ ])),
93
+ }, [roof.id]);
94
+ const originalDefaultId = defaults[0]?.id;
95
+ useScene.getState().updateNode(segment.id, {
96
+ trim: { ...segment.trim, left: 2, frontLeftX: 2, frontLeftZ: 3 },
97
+ });
98
+ const nextSegment = useScene.getState().nodes[segment.id];
99
+ expect(nextSegment?.children).toEqual(defaults.map((vent) => vent.id));
100
+ expect(useScene.getState().nodes[originalDefaultId]).toMatchObject({
101
+ id: originalDefaultId,
102
+ length: defaults[0]?.length,
103
+ position: defaults[0]?.position,
104
+ });
105
+ });
106
+ test('creates default ridge vents after a geometry change when auto ridge vent is enabled', () => {
107
+ const roof = RoofNode.parse({ id: 'roof_test', children: [] });
108
+ const segment = RoofSegmentNode.parse({
109
+ id: 'rseg_test',
110
+ parentId: roof.id,
111
+ roofType: 'flat',
112
+ width: 8,
113
+ depth: 6,
114
+ metadata: { autoRidgeVent: true },
115
+ });
116
+ useScene.getState().setScene({
117
+ [roof.id]: { ...roof, children: [segment.id] },
118
+ [segment.id]: segment,
119
+ }, [roof.id]);
120
+ useScene.getState().updateNode(segment.id, {
121
+ roofType: 'gable',
122
+ });
123
+ const nextSegment = useScene.getState().nodes[segment.id];
124
+ expect(nextSegment?.children).toHaveLength(1);
125
+ expect(useScene.getState().nodes[nextSegment?.children[0]]).toMatchObject({
126
+ type: 'ridge-vent',
127
+ roofSegmentId: segment.id,
128
+ });
129
+ });
130
+ test('regenerates default ridge vents when Dutch auto-vent fields change', () => {
131
+ const roof = RoofNode.parse({ id: 'roof_test', children: [] });
132
+ const segment = RoofSegmentNode.parse({
133
+ id: 'rseg_test',
134
+ parentId: roof.id,
135
+ roofType: 'dutch',
136
+ width: 8,
137
+ depth: 6,
138
+ metadata: { autoRidgeVent: true },
139
+ });
140
+ const defaults = createDefaultRidgeVentsForSegment(segment);
141
+ useScene.getState().setScene({
142
+ [roof.id]: { ...roof, children: [segment.id] },
143
+ [segment.id]: {
144
+ ...segment,
145
+ children: defaults.map((vent) => vent.id),
146
+ },
147
+ ...Object.fromEntries(defaults.map((vent) => [
148
+ vent.id,
149
+ { ...vent, parentId: segment.id, roofSegmentId: segment.id },
150
+ ])),
151
+ }, [roof.id]);
152
+ const originalDefaultIds = defaults.map((vent) => vent.id);
153
+ useScene.getState().updateNode(segment.id, {
154
+ pitch: 52,
155
+ dutchWaistLengthRatio: 0.72,
156
+ dutchGabletRake: 0.9,
157
+ });
158
+ const nextSegment = useScene.getState().nodes[segment.id];
159
+ const nextChildren = nextSegment?.children ?? [];
160
+ expect(nextChildren).toHaveLength(defaults.length);
161
+ expect(nextChildren.some((id) => originalDefaultIds.includes(id))).toBe(false);
162
+ for (const oldId of originalDefaultIds) {
163
+ expect(useScene.getState().nodes[oldId]).toBeUndefined();
164
+ }
165
+ });
166
+ test('refresh replaces legacy default vents that still use preset-white', () => {
167
+ const roof = RoofNode.parse({ id: 'roof_test', children: [] });
168
+ const segment = RoofSegmentNode.parse({
169
+ id: 'rseg_test',
170
+ parentId: roof.id,
171
+ roofType: 'gable',
172
+ width: 8,
173
+ depth: 6,
174
+ metadata: { autoRidgeVent: true },
175
+ });
176
+ const legacyDefault = RidgeVentNode.parse({
177
+ id: 'rvent_legacy',
178
+ parentId: segment.id,
179
+ roofSegmentId: segment.id,
180
+ name: 'Ridge Vent',
181
+ style: 'shingled',
182
+ materialPreset: 'preset-white',
183
+ position: [0, 0, 0],
184
+ length: 8,
185
+ });
186
+ useScene.getState().setScene({
187
+ [roof.id]: { ...roof, children: [segment.id] },
188
+ [segment.id]: {
189
+ ...segment,
190
+ children: [legacyDefault.id],
191
+ },
192
+ [legacyDefault.id]: legacyDefault,
193
+ }, [roof.id]);
194
+ useScene.getState().updateNode(segment.id, {
195
+ pitch: 52,
196
+ });
197
+ const nextSegment = useScene.getState().nodes[segment.id];
198
+ const nextChildren = nextSegment?.children ?? [];
199
+ expect(nextChildren).toHaveLength(1);
200
+ expect(nextChildren[0]).not.toBe(legacyDefault.id);
201
+ expect(useScene.getState().nodes[legacyDefault.id]).toBeUndefined();
202
+ });
203
+ test('refresh preserves user-created default-looking ridge vents without legacy preset metadata', () => {
204
+ const roof = RoofNode.parse({ id: 'roof_test', children: [] });
205
+ const segment = RoofSegmentNode.parse({
206
+ id: 'rseg_test',
207
+ parentId: roof.id,
208
+ roofType: 'gable',
209
+ width: 8,
210
+ depth: 6,
211
+ metadata: { autoRidgeVent: true },
212
+ });
213
+ const userVent = RidgeVentNode.parse({
214
+ id: 'rvent_user',
215
+ parentId: segment.id,
216
+ roofSegmentId: segment.id,
217
+ name: 'Ridge Vent',
218
+ style: 'shingled',
219
+ position: [0, 0, 0],
220
+ length: 8,
221
+ });
222
+ useScene.getState().setScene({
223
+ [roof.id]: { ...roof, children: [segment.id] },
224
+ [segment.id]: {
225
+ ...segment,
226
+ children: [userVent.id],
227
+ },
228
+ [userVent.id]: userVent,
229
+ }, [roof.id]);
230
+ useScene.getState().updateNode(segment.id, {
231
+ pitch: 52,
232
+ });
233
+ const nextSegment = useScene.getState().nodes[segment.id];
234
+ const nextChildren = nextSegment?.children ?? [];
235
+ expect(nextChildren).toContain(userVent.id);
236
+ expect(useScene.getState().nodes[userVent.id]).toMatchObject({
237
+ id: userVent.id,
238
+ roofSegmentId: segment.id,
239
+ name: 'Ridge Vent',
240
+ style: 'shingled',
241
+ length: 8,
242
+ });
243
+ });
244
+ test('does not create default ridge vents after a geometry change when auto ridge vent is disabled', () => {
245
+ const roof = RoofNode.parse({ id: 'roof_test', children: [] });
246
+ const segment = RoofSegmentNode.parse({
247
+ id: 'rseg_test',
248
+ parentId: roof.id,
249
+ roofType: 'flat',
250
+ width: 8,
251
+ depth: 6,
252
+ metadata: { autoRidgeVent: false },
253
+ });
254
+ useScene.getState().setScene({
255
+ [roof.id]: { ...roof, children: [segment.id] },
256
+ [segment.id]: segment,
257
+ }, [roof.id]);
258
+ useScene.getState().updateNode(segment.id, {
259
+ roofType: 'gable',
260
+ });
261
+ const nextSegment = useScene.getState().nodes[segment.id];
262
+ expect(nextSegment?.children ?? []).toHaveLength(0);
263
+ });
264
+ });
@@ -6,9 +6,20 @@ type TemporalStoreLike = {
6
6
  };
7
7
  };
8
8
  };
9
+ type TemporalHistoryStoreLike<TPastState> = {
10
+ temporal: {
11
+ getState(): {
12
+ pastStates: TPastState[];
13
+ };
14
+ setState(state: {
15
+ pastStates: TPastState[];
16
+ }): void;
17
+ };
18
+ };
9
19
  export declare function pauseSceneHistory(sceneStore: TemporalStoreLike): void;
10
20
  export declare function resumeSceneHistory(sceneStore: TemporalStoreLike): void;
11
21
  export declare function getSceneHistoryPauseDepth(): number;
12
22
  export declare function resetSceneHistoryPauseDepth(): void;
23
+ export declare function runAsSingleSceneHistoryStep<TPastState, TResult>(sceneStore: TemporalHistoryStoreLike<TPastState>, run: () => TResult): TResult;
13
24
  export {};
14
25
  //# sourceMappingURL=history-control.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"history-control.d.ts","sourceRoot":"","sources":["../../src/store/history-control.ts"],"names":[],"mappings":"AAEA,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE;QACR,QAAQ,IAAI;YACV,KAAK,IAAI,IAAI,CAAA;YACb,MAAM,IAAI,IAAI,CAAA;SACf,CAAA;KACF,CAAA;CACF,CAAA;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAKrE;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAStE;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
1
+ {"version":3,"file":"history-control.d.ts","sourceRoot":"","sources":["../../src/store/history-control.ts"],"names":[],"mappings":"AAEA,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE;QACR,QAAQ,IAAI;YACV,KAAK,IAAI,IAAI,CAAA;YACb,MAAM,IAAI,IAAI,CAAA;SACf,CAAA;KACF,CAAA;CACF,CAAA;AAED,KAAK,wBAAwB,CAAC,UAAU,IAAI;IAC1C,QAAQ,EAAE;QACR,QAAQ,IAAI;YACV,UAAU,EAAE,UAAU,EAAE,CAAA;SACzB,CAAA;QACD,QAAQ,CAAC,KAAK,EAAE;YAAE,UAAU,EAAE,UAAU,EAAE,CAAA;SAAE,GAAG,IAAI,CAAA;KACpD,CAAA;CACF,CAAA;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAKrE;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAStE;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AAkBD,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,OAAO,EAC7D,UAAU,EAAE,wBAAwB,CAAC,UAAU,CAAC,EAChD,GAAG,EAAE,MAAM,OAAO,GACjB,OAAO,CAeT"}
@@ -20,3 +20,36 @@ export function getSceneHistoryPauseDepth() {
20
20
  export function resetSceneHistoryPauseDepth() {
21
21
  sceneHistoryPauseDepth = 0;
22
22
  }
23
+ function retainedPastStateCount(before, after) {
24
+ for (let start = 0; start < before.length; start += 1) {
25
+ const retained = before.length - start;
26
+ if (retained > after.length)
27
+ continue;
28
+ let matches = true;
29
+ for (let index = 0; index < retained; index += 1) {
30
+ if (before[start + index] !== after[index]) {
31
+ matches = false;
32
+ break;
33
+ }
34
+ }
35
+ if (matches)
36
+ return retained;
37
+ }
38
+ return 0;
39
+ }
40
+ export function runAsSingleSceneHistoryStep(sceneStore, run) {
41
+ const beforePastStates = sceneStore.temporal.getState().pastStates;
42
+ const result = run();
43
+ const afterPastStates = sceneStore.temporal.getState().pastStates;
44
+ const retainedCount = retainedPastStateCount(beforePastStates, afterPastStates);
45
+ const addedCount = afterPastStates.length - retainedCount;
46
+ if (addedCount > 1) {
47
+ const firstAddedState = afterPastStates[retainedCount];
48
+ if (firstAddedState !== undefined) {
49
+ sceneStore.temporal.setState({
50
+ pastStates: [...afterPastStates.slice(0, retainedCount), firstAddedState],
51
+ });
52
+ }
53
+ }
54
+ return result;
55
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-scene-dirty-tracking.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-scene-dirty-tracking.test.d.ts","sourceRoot":"","sources":["../../src/store/use-scene-dirty-tracking.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,68 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test';
2
+ import { nodeRegistry } from '../registry/registry';
3
+ import useScene from './use-scene';
4
+ const untrackedDef = {
5
+ kind: 'test-untracked',
6
+ schemaVersion: 1,
7
+ schema: {},
8
+ category: 'furnishing',
9
+ defaults: () => ({}),
10
+ capabilities: {},
11
+ dirtyTracking: false,
12
+ };
13
+ const trackedDef = {
14
+ ...untrackedDef,
15
+ kind: 'test-tracked',
16
+ dirtyTracking: undefined,
17
+ };
18
+ const UNTRACKED = 'item_untracked';
19
+ const TRACKED = 'item_tracked';
20
+ const UNREGISTERED = 'item_unregistered';
21
+ const makeNode = (id, type) => ({
22
+ object: 'node',
23
+ id,
24
+ type,
25
+ parentId: null,
26
+ visible: true,
27
+ metadata: {},
28
+ children: [],
29
+ });
30
+ describe('dirty tracking', () => {
31
+ beforeEach(() => {
32
+ if (!nodeRegistry.has(untrackedDef.kind))
33
+ nodeRegistry._register(untrackedDef);
34
+ if (!nodeRegistry.has(trackedDef.kind))
35
+ nodeRegistry._register(trackedDef);
36
+ useScene.setState({
37
+ nodes: {
38
+ [UNTRACKED]: makeNode(UNTRACKED, 'test-untracked'),
39
+ [TRACKED]: makeNode(TRACKED, 'test-tracked'),
40
+ [UNREGISTERED]: makeNode(UNREGISTERED, 'unregistered-kind'),
41
+ },
42
+ rootNodeIds: [UNTRACKED, TRACKED, UNREGISTERED],
43
+ dirtyNodes: new Set(),
44
+ collections: {},
45
+ });
46
+ useScene.temporal.getState().clear();
47
+ });
48
+ // Membership asserts (not set size/equality): the scene store is a module
49
+ // singleton, and subscribers leaked by other test files can add their own
50
+ // dirty marks when `setState` fires.
51
+ test('markDirty skips kinds whose definition opts out', () => {
52
+ useScene.getState().markDirty(UNTRACKED);
53
+ expect(useScene.getState().dirtyNodes.has(UNTRACKED)).toBe(false);
54
+ });
55
+ test('markDirty tracks kinds without the opt-out, registered or not', () => {
56
+ useScene.getState().markDirty(TRACKED);
57
+ useScene.getState().markDirty(UNREGISTERED);
58
+ expect(useScene.getState().dirtyNodes.has(TRACKED)).toBe(true);
59
+ expect(useScene.getState().dirtyNodes.has(UNREGISTERED)).toBe(true);
60
+ });
61
+ test('deleteNodes removes deleted ids from the dirty set', () => {
62
+ useScene.getState().markDirty(TRACKED);
63
+ expect(useScene.getState().dirtyNodes.has(TRACKED)).toBe(true);
64
+ useScene.getState().deleteNodes([TRACKED]);
65
+ expect(useScene.getState().nodes[TRACKED]).toBeUndefined();
66
+ expect(useScene.getState().dirtyNodes.has(TRACKED)).toBe(false);
67
+ });
68
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-scene-measurement-roundtrip.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-scene-measurement-roundtrip.test.d.ts","sourceRoot":"","sources":["../../src/store/use-scene-measurement-roundtrip.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,97 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test';
2
+ import { AnyNode, BuildingNode, LevelNode, MeasurementNode, SiteNode, } from '../schema';
3
+ import useScene from './use-scene';
4
+ describe('scene measurement round-trip', () => {
5
+ beforeEach(() => {
6
+ useScene.setState({
7
+ nodes: {},
8
+ rootNodeIds: [],
9
+ dirtyNodes: new Set(),
10
+ collections: {},
11
+ materials: {},
12
+ readOnly: false,
13
+ });
14
+ useScene.temporal.getState().clear();
15
+ });
16
+ test('preserves a complete measurement graph across JSON and setScene', () => {
17
+ const distance = MeasurementNode.parse({
18
+ id: 'measurement_distance',
19
+ parentId: 'level_ground',
20
+ visible: false,
21
+ measurement: {
22
+ kind: 'distance',
23
+ points: [
24
+ [0.25, 0.5, 0.75],
25
+ [4.5, 2.25, 1.5],
26
+ ],
27
+ },
28
+ });
29
+ const area = MeasurementNode.parse({
30
+ id: 'measurement_area',
31
+ parentId: 'level_ground',
32
+ visible: true,
33
+ measurement: {
34
+ kind: 'area',
35
+ base: [
36
+ [0, 0, 0],
37
+ [3, 0, 0],
38
+ [3, 0, 2],
39
+ [0, 0, 2],
40
+ ],
41
+ },
42
+ });
43
+ const volume = MeasurementNode.parse({
44
+ id: 'measurement_volume',
45
+ parentId: 'level_ground',
46
+ visible: false,
47
+ measurement: {
48
+ kind: 'volume',
49
+ base: [
50
+ [1, 0, 1],
51
+ [3, 0, 1],
52
+ [3, 0, 4],
53
+ [1, 0, 4],
54
+ ],
55
+ extrusion: [0.5, 2.5, 0],
56
+ },
57
+ });
58
+ const level = LevelNode.parse({
59
+ id: 'level_ground',
60
+ parentId: 'building_main',
61
+ level: 0,
62
+ children: [distance.id, area.id, volume.id],
63
+ });
64
+ const building = BuildingNode.parse({
65
+ id: 'building_main',
66
+ parentId: 'site_measurements',
67
+ children: [level.id],
68
+ });
69
+ const site = SiteNode.parse({
70
+ id: 'site_measurements',
71
+ children: [building.id],
72
+ });
73
+ const nodes = Object.fromEntries([site, building, level, distance, area, volume].map((node) => [node.id, node]));
74
+ const serialized = JSON.stringify({ nodes, rootNodeIds: [site.id] });
75
+ const decoded = JSON.parse(serialized);
76
+ const parsedNodes = Object.fromEntries(Object.entries(decoded.nodes).map(([id, node]) => [id, AnyNode.parse(node)]));
77
+ useScene.getState().setScene(parsedNodes, decoded.rootNodeIds);
78
+ const reloaded = useScene.getState();
79
+ const reloadedSite = SiteNode.parse(reloaded.nodes[site.id]);
80
+ const reloadedBuilding = BuildingNode.parse(reloaded.nodes[building.id]);
81
+ const reloadedLevel = LevelNode.parse(reloaded.nodes[level.id]);
82
+ const reloadedMeasurements = [distance.id, area.id, volume.id].map((id) => MeasurementNode.parse(AnyNode.parse(reloaded.nodes[id])));
83
+ expect(reloaded.rootNodeIds).toEqual([site.id]);
84
+ expect(reloadedSite.children).toEqual([building.id]);
85
+ expect(reloadedBuilding.children).toEqual([level.id]);
86
+ expect(reloadedLevel.children).toEqual([distance.id, area.id, volume.id]);
87
+ expect(reloadedMeasurements.map((node) => ({
88
+ id: node.id,
89
+ visible: node.visible,
90
+ measurement: node.measurement,
91
+ }))).toEqual([
92
+ { id: distance.id, visible: false, measurement: distance.measurement },
93
+ { id: area.id, visible: true, measurement: area.measurement },
94
+ { id: volume.id, visible: false, measurement: volume.measurement },
95
+ ]);
96
+ });
97
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-scene-plugins.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-scene-plugins.test.d.ts","sourceRoot":"","sources":["../../src/store/use-scene-plugins.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ import { beforeEach, describe, expect, test } from 'bun:test';
2
+ import { z } from 'zod';
3
+ import { loadPlugin, nodeRegistry } from '../registry';
4
+ import useScene from './use-scene';
5
+ describe('scene plugin installation state', () => {
6
+ beforeEach(() => {
7
+ nodeRegistry._reset();
8
+ useScene.getState().setReadOnly(false);
9
+ useScene.getState().unloadScene();
10
+ });
11
+ test('loads an explicit installed plugin list with the scene', () => {
12
+ useScene.getState().setScene({}, [], {
13
+ installedPlugins: ['pascal:trees'],
14
+ hasExplicitPluginInstallState: true,
15
+ });
16
+ expect(useScene.getState().installedPlugins).toEqual(['pascal:trees']);
17
+ expect(useScene.getState().hasExplicitPluginInstallState).toBe(true);
18
+ });
19
+ test('install changes are de-duplicated and become explicit', () => {
20
+ useScene.getState().setInstalledPlugins(['pascal:trees', 'pascal:trees'], { explicit: true });
21
+ expect(useScene.getState().installedPlugins).toEqual(['pascal:trees']);
22
+ expect(useScene.getState().hasExplicitPluginInstallState).toBe(true);
23
+ });
24
+ test('clearing geometry preserves project plugin installs', () => {
25
+ useScene.getState().setInstalledPlugins(['pascal:trees'], { explicit: true });
26
+ useScene.getState().clearScene();
27
+ expect(useScene.getState().installedPlugins).toEqual(['pascal:trees']);
28
+ expect(useScene.getState().hasExplicitPluginInstallState).toBe(true);
29
+ });
30
+ test('uninstall clears plugin build work and reinstall schedules it again', async () => {
31
+ const kind = 'test:plugin-node';
32
+ const definition = {
33
+ kind,
34
+ schemaVersion: 1,
35
+ schema: z.object({ id: z.string(), type: z.literal(kind) }),
36
+ category: 'utility',
37
+ defaults: () => ({}),
38
+ capabilities: {},
39
+ };
40
+ await loadPlugin({ id: 'test:plugin', apiVersion: 1, nodes: [definition] });
41
+ const nodeId = 'plugin_node';
42
+ useScene.getState().setScene({
43
+ [nodeId]: { id: nodeId, type: kind },
44
+ }, [nodeId], { installedPlugins: ['test:plugin'], hasExplicitPluginInstallState: true });
45
+ expect(useScene.getState().dirtyNodes.has(nodeId)).toBe(true);
46
+ useScene.getState().setInstalledPlugins([], { explicit: true });
47
+ expect(useScene.getState().dirtyNodes.has(nodeId)).toBe(false);
48
+ useScene.getState().setInstalledPlugins(['test:plugin'], { explicit: true });
49
+ expect(useScene.getState().dirtyNodes.has(nodeId)).toBe(true);
50
+ });
51
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-scene-wall-slot-migration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-scene-wall-slot-migration.test.d.ts","sourceRoot":"","sources":["../../src/store/use-scene-wall-slot-migration.test.ts"],"names":[],"mappings":""}