@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,32 @@
1
+ import type { AnyNode, AnyNodeId } from '../schema';
2
+ /**
3
+ * IPC validators for the DWV (drain-waste-vent) system — the "CodeRule"
4
+ * primitive from the domain brief. The slope, minimum-size, and
5
+ * trap-arm rules are all geometric and read straight off the node
6
+ * fields, so they live here in core (pure logic) where the editor can
7
+ * surface them and analyses can reuse them.
8
+ *
9
+ * Scope is residential IPC, simplified:
10
+ * - 704.1 drainage slope by pipe size.
11
+ * - 909 trap-arm maximum developed length by trap size.
12
+ *
13
+ * These are intentionally conservative approximations, not a certified
14
+ * plan-check — enough to flag the mistakes a drawing tool invites.
15
+ */
16
+ /** Drainage findings, worst-first per consumer's sort. */
17
+ export type DwvSeverity = 'error' | 'warning';
18
+ export type DwvFinding = {
19
+ severity: DwvSeverity;
20
+ /** Stable rule id, e.g. 'slope-too-flat'. */
21
+ code: string;
22
+ /** Human-readable, already-formatted message. */
23
+ message: string;
24
+ /** Nodes the finding implicates (usually one). */
25
+ nodeIds: AnyNodeId[];
26
+ };
27
+ /**
28
+ * Run every DWV rule over the scene and return the findings. Empty
29
+ * array = nothing to flag. Pure: no scene/store access, no rendering.
30
+ */
31
+ export declare function validateDwv(nodes: Readonly<Record<AnyNodeId, AnyNode>>): DwvFinding[];
32
+ //# sourceMappingURL=validate-dwv.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-dwv.d.ts","sourceRoot":"","sources":["../../src/services/validate-dwv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAGnD;;;;;;;;;;;;;GAaG;AAEH,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,SAAS,CAAA;AAE7C,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,WAAW,CAAA;IACrB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,kDAAkD;IAClD,OAAO,EAAE,SAAS,EAAE,CAAA;CACrB,CAAA;AAuDD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,GAAG,UAAU,EAAE,CAwErF"}
@@ -0,0 +1,118 @@
1
+ import { buildPortComponents } from './system-graph';
2
+ /** IPC 704.1 minimum drainage slope (rise/run, dimensionless) by
3
+ * nominal pipe size: ¼"/ft (1:48) under 3", ⅛"/ft (1:96) for 3–6",
4
+ * 1/16"/ft (1:192) at 8"+. */
5
+ function minSlopeFor(diameterIn) {
6
+ if (diameterIn < 3)
7
+ return 1 / 48;
8
+ if (diameterIn < 8)
9
+ return 1 / 96;
10
+ return 1 / 192;
11
+ }
12
+ /** IPC Table 909.1 maximum trap-arm developed length (meters) by trap
13
+ * size: 30" @ 1¼", 42" @ 1½", 60" @ 2", 72" @ 3", 120" @ 4". */
14
+ const TRAP_ARM_MAX_M = [
15
+ [1.25, 30 * 0.0254],
16
+ [1.5, 42 * 0.0254],
17
+ [2, 60 * 0.0254],
18
+ [3, 72 * 0.0254],
19
+ [4, 120 * 0.0254],
20
+ ];
21
+ function trapArmMaxFor(diameterIn) {
22
+ let max = Infinity;
23
+ for (const [size, lengthM] of TRAP_ARM_MAX_M) {
24
+ if (diameterIn <= size)
25
+ return lengthM;
26
+ max = lengthM;
27
+ }
28
+ return max;
29
+ }
30
+ /** Slopes shallower than this fraction of the minimum are flagged
31
+ * "too flat" — a small tolerance keeps round-off off the list. */
32
+ const SLOPE_TOLERANCE = 0.9;
33
+ /** Horizontal legs shorter than this (meters) are treated as vertical
34
+ * stacks and skipped from the slope check. */
35
+ const VERTICAL_LEG_EPS_M = 0.02;
36
+ function legSlope(a, b) {
37
+ const horizontalM = Math.hypot(b[0] - a[0], b[2] - a[2]);
38
+ if (horizontalM < VERTICAL_LEG_EPS_M)
39
+ return { horizontalM, slope: Infinity };
40
+ return { horizontalM, slope: Math.abs(a[1] - b[1]) / horizontalM };
41
+ }
42
+ function inchLabel(value) {
43
+ return `${value}"`;
44
+ }
45
+ /** Per-foot slope as a readable fraction, e.g. 0.0208 → '¼"/ft'. */
46
+ function slopePerFootLabel(slope) {
47
+ const inchesPerFoot = slope * 12;
48
+ return `${inchesPerFoot.toFixed(2)}"/ft`;
49
+ }
50
+ /**
51
+ * Run every DWV rule over the scene and return the findings. Empty
52
+ * array = nothing to flag. Pure: no scene/store access, no rendering.
53
+ */
54
+ export function validateDwv(nodes) {
55
+ const findings = [];
56
+ // ── Per-segment slope (waste only) ──────────────────────────────
57
+ for (const node of Object.values(nodes)) {
58
+ if (node?.type !== 'pipe-segment' || node.system !== 'waste')
59
+ continue;
60
+ const path = node.path;
61
+ const minSlope = minSlopeFor(node.diameter);
62
+ const maxSlope = node.diameter / 12; // 1 pipe-diameter per foot → siphoning
63
+ let flaggedFlat = false;
64
+ let flaggedSteep = false;
65
+ for (let i = 0; i < path.length - 1; i++) {
66
+ const { slope } = legSlope(path[i], path[i + 1]);
67
+ if (slope === Infinity)
68
+ continue; // vertical stack leg
69
+ if (!flaggedFlat && slope < minSlope * SLOPE_TOLERANCE) {
70
+ findings.push({
71
+ severity: 'error',
72
+ code: 'slope-too-flat',
73
+ message: `${inchLabel(node.diameter)} drain slopes ${slopePerFootLabel(slope)} — IPC 704.1 requires at least ${slopePerFootLabel(minSlope)}.`,
74
+ nodeIds: [node.id],
75
+ });
76
+ flaggedFlat = true;
77
+ }
78
+ if (!flaggedSteep && slope > maxSlope) {
79
+ findings.push({
80
+ severity: 'warning',
81
+ code: 'slope-too-steep',
82
+ message: `${inchLabel(node.diameter)} drain slopes ${slopePerFootLabel(slope)} — over one pipe-diameter per foot risks siphoning the traps.`,
83
+ nodeIds: [node.id],
84
+ });
85
+ flaggedSteep = true;
86
+ }
87
+ }
88
+ }
89
+ // ── Component-scoped trap rules ──────────────────────────────────
90
+ for (const component of buildPortComponents(nodes)) {
91
+ const traps = [];
92
+ for (const id of component) {
93
+ const node = nodes[id];
94
+ if (!node)
95
+ continue;
96
+ if (node.type === 'pipe-trap') {
97
+ traps.push(node);
98
+ }
99
+ }
100
+ // Trap-arm developed length: trap outlet → its vent, capped by size.
101
+ // Independent of waste segments — a trap on its own can already be
102
+ // over-armed.
103
+ for (const trap of traps) {
104
+ const t = trap;
105
+ const armLengthM = t.armLengthM ?? 0;
106
+ const maxArm = trapArmMaxFor(t.diameter);
107
+ if (armLengthM > maxArm + 1e-6) {
108
+ findings.push({
109
+ severity: 'error',
110
+ code: 'trap-arm-too-long',
111
+ message: `${inchLabel(t.diameter)} trap arm runs ${(armLengthM / 0.0254).toFixed(0)}" to its vent — IPC 909.1 caps it at ${(maxArm / 0.0254).toFixed(0)}".`,
112
+ nodeIds: [t.id],
113
+ });
114
+ }
115
+ }
116
+ }
117
+ return findings;
118
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=validate-dwv.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-dwv.test.d.ts","sourceRoot":"","sources":["../../src/services/validate-dwv.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,109 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { registerNode } from '../registry';
3
+ import { validateDwv } from './validate-dwv';
4
+ // The validator reads node fields directly + buildPortComponents (which
5
+ // consults def.ports), so register stub port-providers for the DWV kinds
6
+ // it groups by. Mirrors the system-graph test's approach.
7
+ function stubDef(kind, ports) {
8
+ registerNode({
9
+ kind,
10
+ schemaVersion: 1,
11
+ schema: {},
12
+ category: 'utility',
13
+ defaults: () => ({}),
14
+ capabilities: {},
15
+ ports,
16
+ });
17
+ }
18
+ stubDef('pipe-segment', (node) => {
19
+ const path = node.path;
20
+ const diameter = node.diameter;
21
+ const system = node.system;
22
+ return [
23
+ { id: 'start', position: path[0], direction: [-1, 0, 0], diameter, system },
24
+ {
25
+ id: 'end',
26
+ position: path[path.length - 1],
27
+ direction: [1, 0, 0],
28
+ diameter,
29
+ system,
30
+ },
31
+ ];
32
+ });
33
+ stubDef('pipe-trap', (node) => {
34
+ const position = node.position;
35
+ return [{ id: 'inlet', position, direction: [0, 1, 0], diameter: 1.5, system: 'waste' }];
36
+ });
37
+ let nextId = 0;
38
+ function makeNode(type, fields) {
39
+ nextId += 1;
40
+ return { id: `${type}_${nextId}`, type, object: 'node', parentId: null, ...fields };
41
+ }
42
+ function sceneOf(...nodes) {
43
+ return Object.fromEntries(nodes.map((n) => [n.id, n]));
44
+ }
45
+ /** A waste run from a→b. Drop the end Y to slope it. */
46
+ function waste(path, diameter = 2) {
47
+ return makeNode('pipe-segment', { path, diameter, system: 'waste' });
48
+ }
49
+ const QUARTER_PER_FOOT = 1 / 48;
50
+ describe('validateDwv — slope', () => {
51
+ test('flags a flat waste run', () => {
52
+ const run = waste([
53
+ [0, 0, 0],
54
+ [3, 0, 0], // dead level
55
+ ]);
56
+ const findings = validateDwv(sceneOf(run));
57
+ expect(findings.some((f) => f.code === 'slope-too-flat')).toBe(true);
58
+ });
59
+ test('passes a run sloped at quarter-inch per foot', () => {
60
+ const drop = 3 * QUARTER_PER_FOOT;
61
+ const run = waste([
62
+ [0, 0, 0],
63
+ [3, -drop, 0],
64
+ ]);
65
+ const findings = validateDwv(sceneOf(run));
66
+ expect(findings.some((f) => f.code === 'slope-too-flat')).toBe(false);
67
+ });
68
+ test('flags an over-steep run (siphoning risk)', () => {
69
+ // 2" pipe, max slope = 2/12 ≈ 0.167; drop 2m over 1m horizontal.
70
+ const run = waste([
71
+ [0, 0, 0],
72
+ [1, -2, 0],
73
+ ]);
74
+ const findings = validateDwv(sceneOf(run));
75
+ expect(findings.some((f) => f.code === 'slope-too-steep')).toBe(true);
76
+ });
77
+ test('ignores vents (level is fine)', () => {
78
+ const vent = makeNode('pipe-segment', {
79
+ path: [
80
+ [0, 0, 0],
81
+ [0, 3, 0],
82
+ ],
83
+ diameter: 2,
84
+ system: 'vent',
85
+ });
86
+ const findings = validateDwv(sceneOf(vent));
87
+ expect(findings.length).toBe(0);
88
+ });
89
+ });
90
+ describe('validateDwv — trap arm', () => {
91
+ test('flags an over-long trap arm', () => {
92
+ const trap = makeNode('pipe-trap', {
93
+ position: [0, 0, 0],
94
+ diameter: 1.5, // max arm 42in = 1.067m
95
+ armLengthM: 2, // way over
96
+ });
97
+ const findings = validateDwv(sceneOf(trap));
98
+ expect(findings.some((f) => f.code === 'trap-arm-too-long')).toBe(true);
99
+ });
100
+ test('passes a trap arm within the limit', () => {
101
+ const trap = makeNode('pipe-trap', {
102
+ position: [0, 0, 0],
103
+ diameter: 2, // max arm 60in = 1.524m
104
+ armLengthM: 1,
105
+ });
106
+ const findings = validateDwv(sceneOf(trap));
107
+ expect(findings.some((f) => f.code === 'trap-arm-too-long')).toBe(false);
108
+ });
109
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EAKf,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAC3D,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AAC7D,KAAK,YAAY,GAAG,SAAS,CAAA;AAqrB7B,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,YAAY,EAAE,SAkDpB,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,SAoIvF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SA2ErD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SAsGjB,CAAA"}
1
+ {"version":3,"file":"node-actions.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-actions.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,SAAS,EASf,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAG9C,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAAE,CAAA;AAC3D,KAAK,YAAY,GAAG;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AAC7D,KAAK,YAAY,GAAG,SAAS,CAAA;AA0uB7B,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,YAAY,EAAE,SAkDpB,CAAA;AAED,eAAO,MAAM,sBAAsB,GACjC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,SAyIvF,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,SAAS;IAAE,EAAE,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,EAAE,SAoFrD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAC5B,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,EAC7D,KAAK,MAAM,UAAU,EACrB,KAAK,SAAS,EAAE,SAqIjB,CAAA"}
@@ -1,4 +1,19 @@
1
- import { AnyNode as AnyNodeSchema, getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, } from '../../schema';
1
+ import { nodeRegistry } from '../../registry/registry';
2
+ import { AnyNode as AnyNodeSchema, createDefaultRidgeVentsForSegment, getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, isAutoRidgeVentEnabled, isDefaultRidgeVentNode, } from '../../schema';
3
+ const DEFAULT_RIDGE_VENT_REFRESH_FIELDS = new Set([
4
+ 'roofType',
5
+ 'width',
6
+ 'depth',
7
+ 'pitch',
8
+ 'overhang',
9
+ 'wallThickness',
10
+ 'shingleThickness',
11
+ 'gambrelLowerWidthRatio',
12
+ 'mansardSteepWidthRatio',
13
+ 'dutchHipWidthRatio',
14
+ 'dutchWaistLengthRatio',
15
+ 'dutchGabletRake',
16
+ ]);
2
17
  const NUMBER_FORMAT_BOUNDS = {
3
18
  safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
4
19
  int32: [-2147483648, 2147483647],
@@ -341,6 +356,34 @@ function parseUpdatedNode(currentNode, data) {
341
356
  warnSanitizedNodeMutation('update', currentNode.id, sanitized.issues);
342
357
  return { ...currentNode, ...sanitized.value };
343
358
  }
359
+ function shouldRefreshDefaultRidgeVents(data) {
360
+ return Object.keys(data).some((key) => DEFAULT_RIDGE_VENT_REFRESH_FIELDS.has(key));
361
+ }
362
+ function refreshDefaultRidgeVentsForSegment(nextNodes, segment) {
363
+ const childIds = Array.isArray(segment.children) ? segment.children : [];
364
+ if (!isAutoRidgeVentEnabled(segment, nextNodes))
365
+ return [];
366
+ const defaultIds = childIds.filter((childId) => isDefaultRidgeVentNode(nextNodes[childId], segment.id));
367
+ const defaultIdSet = new Set(defaultIds);
368
+ for (const id of defaultIds) {
369
+ delete nextNodes[id];
370
+ }
371
+ const nextVents = createDefaultRidgeVentsForSegment(segment);
372
+ for (const vent of nextVents) {
373
+ nextNodes[vent.id] = {
374
+ ...vent,
375
+ parentId: segment.id,
376
+ };
377
+ }
378
+ nextNodes[segment.id] = {
379
+ ...segment,
380
+ children: [
381
+ ...childIds.filter((childId) => !defaultIdSet.has(childId)),
382
+ ...nextVents.map((vent) => vent.id),
383
+ ],
384
+ };
385
+ return nextVents.map((vent) => vent.id);
386
+ }
344
387
  // Track pending RAF for updateNodesAction to prevent multiple queued callbacks
345
388
  let pendingRafId = null;
346
389
  let pendingUpdates = new Set();
@@ -575,6 +618,11 @@ export const applyNodeChangesAction = (set, get, changes) => {
575
618
  }
576
619
  }
577
620
  nextNodes[id] = updatedNode;
621
+ if (updatedNode.type === 'roof-segment' && shouldRefreshDefaultRidgeVents(data)) {
622
+ for (const ventId of refreshDefaultRidgeVentsForSegment(nextNodes, updatedNode)) {
623
+ nodesToMarkDirty.add(ventId);
624
+ }
625
+ }
578
626
  nodesToMarkDirty.add(id);
579
627
  }
580
628
  for (const { node, parentId } of createOps) {
@@ -659,6 +707,7 @@ export const updateNodesAction = (set, get, updates) => {
659
707
  if (get().readOnly)
660
708
  return;
661
709
  const parentsToUpdate = new Set();
710
+ const extraNodesToUpdate = new Set();
662
711
  set((state) => {
663
712
  const nextNodes = { ...state.nodes };
664
713
  for (const { id, data } of updates) {
@@ -702,6 +751,11 @@ export const updateNodesAction = (set, get, updates) => {
702
751
  }
703
752
  // Apply the update
704
753
  nextNodes[id] = updatedNode;
754
+ if (updatedNode.type === 'roof-segment' && shouldRefreshDefaultRidgeVents(data)) {
755
+ for (const ventId of refreshDefaultRidgeVentsForSegment(nextNodes, updatedNode)) {
756
+ extraNodesToUpdate.add(ventId);
757
+ }
758
+ }
705
759
  }
706
760
  return { nodes: nextNodes };
707
761
  });
@@ -712,6 +766,9 @@ export const updateNodesAction = (set, get, updates) => {
712
766
  for (const pId of parentsToUpdate) {
713
767
  pendingUpdates.add(pId);
714
768
  }
769
+ for (const id of extraNodesToUpdate) {
770
+ pendingUpdates.add(id);
771
+ }
715
772
  if (pendingRafId !== null) {
716
773
  cancelAnimationFrame(pendingRafId);
717
774
  }
@@ -728,6 +785,7 @@ export const deleteNodesAction = (set, get, ids) => {
728
785
  return;
729
786
  const parentsToMarkDirty = new Set();
730
787
  const nodesToMarkDirty = new Set();
788
+ const deletedIds = new Set();
731
789
  const mergePlans = buildWallMergePlans(get().nodes, ids);
732
790
  set((state) => {
733
791
  const nextNodes = { ...state.nodes };
@@ -741,6 +799,13 @@ export const deleteNodesAction = (set, get, ids) => {
741
799
  return;
742
800
  allIds.add(id);
743
801
  const node = nextNodes[id];
802
+ const cascadeDeletes = node
803
+ ? nodeRegistry.get(node.type)?.parametrics?.onDeleteCascade?.(node, nextNodes, allIds)
804
+ : null;
805
+ if (cascadeDeletes) {
806
+ for (const companionId of cascadeDeletes)
807
+ collect(companionId);
808
+ }
744
809
  if (node && 'children' in node) {
745
810
  for (const cid of node.children)
746
811
  collect(cid);
@@ -751,6 +816,29 @@ export const deleteNodesAction = (set, get, ids) => {
751
816
  for (const plan of mergePlans) {
752
817
  allIds.add(plan.secondaryWallId);
753
818
  }
819
+ for (const id of allIds)
820
+ deletedIds.add(id);
821
+ // Let each deleted kind undo what it imposed on its neighbours (e.g. an
822
+ // auto-inserted elbow re-extends the duct runs it trimmed back onto the
823
+ // corner it replaced). Read against pre-deletion `nextNodes`; skip
824
+ // patches that target a node also being deleted.
825
+ for (const id of allIds) {
826
+ const node = nextNodes[id];
827
+ if (!node)
828
+ continue;
829
+ const onDelete = nodeRegistry.get(node.type)?.parametrics?.onDelete;
830
+ if (!onDelete)
831
+ continue;
832
+ for (const { id: targetId, data } of onDelete(node, nextNodes)) {
833
+ if (allIds.has(targetId))
834
+ continue;
835
+ const target = nextNodes[targetId];
836
+ if (!target)
837
+ continue;
838
+ nextNodes[targetId] = { ...target, ...data };
839
+ nodesToMarkDirty.add(targetId);
840
+ }
841
+ }
754
842
  for (const plan of mergePlans) {
755
843
  const primaryWall = nextNodes[plan.primaryWallId];
756
844
  if (!(primaryWall && primaryWall.type === 'wall') || allIds.has(plan.primaryWallId)) {
@@ -805,6 +893,11 @@ export const deleteNodesAction = (set, get, ids) => {
805
893
  }
806
894
  return { nodes: nextNodes, rootNodeIds: nextRootIds, collections: nextCollections };
807
895
  });
896
+ // Deleted ids must leave the dirty set: every consumer skips missing
897
+ // nodes without clearing them, so a mark on a deleted node would sit in
898
+ // the set (and defeat the consumers' empty-set early exit) forever.
899
+ for (const id of deletedIds)
900
+ get().clearDirty(id);
808
901
  // Mark affected nodes dirty: parents of deleted nodes and their remaining children
809
902
  // (e.g. deleting a slab affects sibling walls via level elevation changes)
810
903
  parentsToMarkDirty.forEach((parentId) => {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ridge-vent-update.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ridge-vent-update.test.d.ts","sourceRoot":"","sources":["../../../src/store/actions/ridge-vent-update.test.ts"],"names":[],"mappings":""}