@pascal-app/core 0.7.0 → 0.9.1

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 (318) hide show
  1. package/dist/events/bus.d.ts +47 -18
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/events/bus.js +1 -1
  4. package/dist/hooks/scene-registry/scene-registry.d.ts +6 -22
  5. package/dist/hooks/scene-registry/scene-registry.d.ts.map +1 -1
  6. package/dist/hooks/scene-registry/scene-registry.js +33 -26
  7. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts +14 -0
  8. package/dist/hooks/spatial-grid/floor-placed-elevation.d.ts.map +1 -0
  9. package/dist/hooks/spatial-grid/floor-placed-elevation.js +51 -0
  10. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts +2 -0
  11. package/dist/hooks/spatial-grid/floor-placed-elevation.test.d.ts.map +1 -0
  12. package/dist/hooks/spatial-grid/floor-placed-elevation.test.js +273 -0
  13. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +13 -2
  14. package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
  15. package/dist/hooks/spatial-grid/spatial-grid-manager.js +95 -9
  16. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +28 -1
  17. package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
  18. package/dist/hooks/spatial-grid/spatial-grid-sync.js +91 -18
  19. package/dist/index.d.ts +22 -4
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +19 -2
  22. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  23. package/dist/lib/polygon-relations.d.ts +10 -0
  24. package/dist/lib/polygon-relations.d.ts.map +1 -0
  25. package/dist/lib/polygon-relations.js +73 -0
  26. package/dist/lib/space-detection-pause.test.d.ts +2 -0
  27. package/dist/lib/space-detection-pause.test.d.ts.map +1 -0
  28. package/dist/lib/space-detection-pause.test.js +42 -0
  29. package/dist/lib/space-detection.d.ts +30 -1
  30. package/dist/lib/space-detection.d.ts.map +1 -1
  31. package/dist/lib/space-detection.js +222 -41
  32. package/dist/lib/space-detection.test.d.ts +2 -0
  33. package/dist/lib/space-detection.test.d.ts.map +1 -0
  34. package/dist/lib/space-detection.test.js +77 -0
  35. package/dist/material-library.d.ts +1 -1
  36. package/dist/material-library.d.ts.map +1 -1
  37. package/dist/material-library.js +1957 -141
  38. package/dist/registry/__bench__/relations-resolver.bench.d.ts +15 -0
  39. package/dist/registry/__bench__/relations-resolver.bench.d.ts.map +1 -0
  40. package/dist/registry/__bench__/relations-resolver.bench.js +170 -0
  41. package/dist/registry/handles.d.ts +321 -0
  42. package/dist/registry/handles.d.ts.map +1 -0
  43. package/dist/registry/handles.js +24 -0
  44. package/dist/registry/index.d.ts +7 -0
  45. package/dist/registry/index.d.ts.map +1 -0
  46. package/dist/registry/index.js +4 -0
  47. package/dist/registry/registry.d.ts +93 -0
  48. package/dist/registry/registry.d.ts.map +1 -0
  49. package/dist/registry/registry.js +201 -0
  50. package/dist/registry/registry.test.d.ts +2 -0
  51. package/dist/registry/registry.test.d.ts.map +1 -0
  52. package/dist/registry/registry.test.js +183 -0
  53. package/dist/registry/relations-resolver.d.ts +42 -0
  54. package/dist/registry/relations-resolver.d.ts.map +1 -0
  55. package/dist/registry/relations-resolver.js +77 -0
  56. package/dist/registry/relations-resolver.test.d.ts +2 -0
  57. package/dist/registry/relations-resolver.test.d.ts.map +1 -0
  58. package/dist/registry/relations-resolver.test.js +183 -0
  59. package/dist/registry/scene-api.d.ts +44 -0
  60. package/dist/registry/scene-api.d.ts.map +1 -0
  61. package/dist/registry/scene-api.js +111 -0
  62. package/dist/registry/scene-api.test.d.ts +2 -0
  63. package/dist/registry/scene-api.test.d.ts.map +1 -0
  64. package/dist/registry/scene-api.test.js +183 -0
  65. package/dist/registry/subtree.d.ts +67 -0
  66. package/dist/registry/subtree.d.ts.map +1 -0
  67. package/dist/registry/subtree.js +119 -0
  68. package/dist/registry/subtree.test.d.ts +2 -0
  69. package/dist/registry/subtree.test.d.ts.map +1 -0
  70. package/dist/registry/subtree.test.js +133 -0
  71. package/dist/registry/types.d.ts +1420 -0
  72. package/dist/registry/types.d.ts.map +1 -0
  73. package/dist/registry/types.js +1 -0
  74. package/dist/schema/asset-url.test.js +0 -4
  75. package/dist/schema/index.d.ts +17 -2
  76. package/dist/schema/index.d.ts.map +1 -1
  77. package/dist/schema/index.js +17 -3
  78. package/dist/schema/material.d.ts +12 -2
  79. package/dist/schema/material.d.ts.map +1 -1
  80. package/dist/schema/material.js +10 -0
  81. package/dist/schema/nodes/box-vent.d.ts +74 -0
  82. package/dist/schema/nodes/box-vent.d.ts.map +1 -0
  83. package/dist/schema/nodes/box-vent.js +63 -0
  84. package/dist/schema/nodes/building.d.ts +1 -1
  85. package/dist/schema/nodes/building.d.ts.map +1 -1
  86. package/dist/schema/nodes/building.js +3 -2
  87. package/dist/schema/nodes/ceiling.d.ts +3 -1
  88. package/dist/schema/nodes/ceiling.d.ts.map +1 -1
  89. package/dist/schema/nodes/ceiling.js +1 -1
  90. package/dist/schema/nodes/chimney.d.ts +158 -0
  91. package/dist/schema/nodes/chimney.d.ts.map +1 -0
  92. package/dist/schema/nodes/chimney.js +67 -0
  93. package/dist/schema/nodes/column.d.ts +598 -6
  94. package/dist/schema/nodes/column.d.ts.map +1 -1
  95. package/dist/schema/nodes/column.js +586 -6
  96. package/dist/schema/nodes/cupola.d.ts +66 -0
  97. package/dist/schema/nodes/cupola.d.ts.map +1 -0
  98. package/dist/schema/nodes/cupola.js +28 -0
  99. package/dist/schema/nodes/door.d.ts +1 -1
  100. package/dist/schema/nodes/dormer.d.ts +228 -0
  101. package/dist/schema/nodes/dormer.d.ts.map +1 -0
  102. package/dist/schema/nodes/dormer.js +119 -0
  103. package/dist/schema/nodes/downspout.d.ts +75 -0
  104. package/dist/schema/nodes/downspout.d.ts.map +1 -0
  105. package/dist/schema/nodes/downspout.js +63 -0
  106. package/dist/schema/nodes/elevator.d.ts +105 -0
  107. package/dist/schema/nodes/elevator.d.ts.map +1 -0
  108. package/dist/schema/nodes/elevator.js +54 -0
  109. package/dist/schema/nodes/eyebrow-vent.d.ts +68 -0
  110. package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -0
  111. package/dist/schema/nodes/eyebrow-vent.js +40 -0
  112. package/dist/schema/nodes/fence.d.ts +3 -2
  113. package/dist/schema/nodes/fence.d.ts.map +1 -1
  114. package/dist/schema/nodes/fence.js +4 -2
  115. package/dist/schema/nodes/gutter.d.ts +84 -0
  116. package/dist/schema/nodes/gutter.d.ts.map +1 -0
  117. package/dist/schema/nodes/gutter.js +76 -0
  118. package/dist/schema/nodes/item.d.ts +16 -0
  119. package/dist/schema/nodes/item.d.ts.map +1 -1
  120. package/dist/schema/nodes/item.js +21 -0
  121. package/dist/schema/nodes/level.d.ts +1 -1
  122. package/dist/schema/nodes/level.d.ts.map +1 -1
  123. package/dist/schema/nodes/level.js +2 -0
  124. package/dist/schema/nodes/ridge-vent.d.ts +67 -0
  125. package/dist/schema/nodes/ridge-vent.d.ts.map +1 -0
  126. package/dist/schema/nodes/ridge-vent.js +30 -0
  127. package/dist/schema/nodes/roof-segment.d.ts +198 -4
  128. package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
  129. package/dist/schema/nodes/roof-segment.js +254 -3
  130. package/dist/schema/nodes/roof.d.ts +6 -6
  131. package/dist/schema/nodes/roof.d.ts.map +1 -1
  132. package/dist/schema/nodes/roof.js +9 -21
  133. package/dist/schema/nodes/shelf.d.ts +105 -0
  134. package/dist/schema/nodes/shelf.d.ts.map +1 -0
  135. package/dist/schema/nodes/shelf.js +84 -0
  136. package/dist/schema/nodes/site.d.ts +1 -116
  137. package/dist/schema/nodes/site.d.ts.map +1 -1
  138. package/dist/schema/nodes/site.js +2 -6
  139. package/dist/schema/nodes/skylight.d.ts +279 -0
  140. package/dist/schema/nodes/skylight.d.ts.map +1 -0
  141. package/dist/schema/nodes/skylight.js +152 -0
  142. package/dist/schema/nodes/slab.d.ts +3 -1
  143. package/dist/schema/nodes/slab.d.ts.map +1 -1
  144. package/dist/schema/nodes/slab.js +1 -1
  145. package/dist/schema/nodes/solar-panel.d.ts +117 -0
  146. package/dist/schema/nodes/solar-panel.d.ts.map +1 -0
  147. package/dist/schema/nodes/solar-panel.js +47 -0
  148. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  149. package/dist/schema/nodes/stair.d.ts +6 -6
  150. package/dist/schema/nodes/stair.d.ts.map +1 -1
  151. package/dist/schema/nodes/stair.js +9 -7
  152. package/dist/schema/nodes/surface-hole-metadata.d.ts +2 -0
  153. package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -1
  154. package/dist/schema/nodes/surface-hole-metadata.js +3 -1
  155. package/dist/schema/nodes/turbine-vent.d.ts +68 -0
  156. package/dist/schema/nodes/turbine-vent.d.ts.map +1 -0
  157. package/dist/schema/nodes/turbine-vent.js +41 -0
  158. package/dist/schema/nodes/wall.d.ts +3 -3
  159. package/dist/schema/nodes/window.d.ts +1 -1
  160. package/dist/schema/types.d.ts +1448 -236
  161. package/dist/schema/types.d.ts.map +1 -1
  162. package/dist/schema/types.js +26 -0
  163. package/dist/services/alignment-anchors.d.ts +100 -0
  164. package/dist/services/alignment-anchors.d.ts.map +1 -0
  165. package/dist/services/alignment-anchors.js +283 -0
  166. package/dist/services/alignment-anchors.test.d.ts +2 -0
  167. package/dist/services/alignment-anchors.test.d.ts.map +1 -0
  168. package/dist/services/alignment-anchors.test.js +383 -0
  169. package/dist/services/alignment.d.ts +126 -0
  170. package/dist/services/alignment.d.ts.map +1 -0
  171. package/dist/services/alignment.js +169 -0
  172. package/dist/services/alignment.test.d.ts +2 -0
  173. package/dist/services/alignment.test.d.ts.map +1 -0
  174. package/dist/services/alignment.test.js +92 -0
  175. package/dist/services/drag-session.d.ts +47 -0
  176. package/dist/services/drag-session.d.ts.map +1 -0
  177. package/dist/services/drag-session.js +100 -0
  178. package/dist/services/drag-session.test.d.ts +2 -0
  179. package/dist/services/drag-session.test.d.ts.map +1 -0
  180. package/dist/services/drag-session.test.js +219 -0
  181. package/dist/services/hosting.d.ts +80 -0
  182. package/dist/services/hosting.d.ts.map +1 -0
  183. package/dist/services/hosting.js +122 -0
  184. package/dist/services/hosting.test.d.ts +2 -0
  185. package/dist/services/hosting.test.d.ts.map +1 -0
  186. package/dist/services/hosting.test.js +199 -0
  187. package/dist/services/index.d.ts +7 -0
  188. package/dist/services/index.d.ts.map +1 -0
  189. package/dist/services/index.js +6 -0
  190. package/dist/services/movement.d.ts +48 -0
  191. package/dist/services/movement.d.ts.map +1 -0
  192. package/dist/services/movement.js +65 -0
  193. package/dist/services/movement.test.d.ts +2 -0
  194. package/dist/services/movement.test.d.ts.map +1 -0
  195. package/dist/services/movement.test.js +133 -0
  196. package/dist/services/single-undo-dance.test.d.ts +2 -0
  197. package/dist/services/single-undo-dance.test.d.ts.map +1 -0
  198. package/dist/services/single-undo-dance.test.js +210 -0
  199. package/dist/services/snap.d.ts +72 -0
  200. package/dist/services/snap.d.ts.map +1 -0
  201. package/dist/services/snap.js +120 -0
  202. package/dist/services/snap.test.d.ts +2 -0
  203. package/dist/services/snap.test.d.ts.map +1 -0
  204. package/dist/services/snap.test.js +95 -0
  205. package/dist/solar-panel-presets.d.ts +17 -0
  206. package/dist/solar-panel-presets.d.ts.map +1 -0
  207. package/dist/solar-panel-presets.js +43 -0
  208. package/dist/store/actions/node-actions.d.ts +14 -2
  209. package/dist/store/actions/node-actions.d.ts.map +1 -1
  210. package/dist/store/actions/node-actions.js +499 -20
  211. package/dist/store/actions/node-mutation-sanitize.test.d.ts +2 -0
  212. package/dist/store/actions/node-mutation-sanitize.test.d.ts.map +1 -0
  213. package/dist/store/actions/node-mutation-sanitize.test.js +147 -0
  214. package/dist/store/actions/reparent.test.d.ts +2 -0
  215. package/dist/store/actions/reparent.test.d.ts.map +1 -0
  216. package/dist/store/actions/reparent.test.js +212 -0
  217. package/dist/store/use-interactive.d.ts +39 -0
  218. package/dist/store/use-interactive.d.ts.map +1 -1
  219. package/dist/store/use-interactive.js +75 -0
  220. package/dist/store/use-live-node-overrides.d.ts +22 -0
  221. package/dist/store/use-live-node-overrides.d.ts.map +1 -0
  222. package/dist/store/use-live-node-overrides.js +59 -0
  223. package/dist/store/use-scene-elevator-migration.test.d.ts +2 -0
  224. package/dist/store/use-scene-elevator-migration.test.d.ts.map +1 -0
  225. package/dist/store/use-scene-elevator-migration.test.js +109 -0
  226. package/dist/store/use-scene.d.ts +11 -0
  227. package/dist/store/use-scene.d.ts.map +1 -1
  228. package/dist/store/use-scene.js +207 -11
  229. package/dist/systems/elevator/elevator-dispatch.d.ts +12 -0
  230. package/dist/systems/elevator/elevator-dispatch.d.ts.map +1 -0
  231. package/dist/systems/elevator/elevator-dispatch.js +57 -0
  232. package/dist/systems/elevator/elevator-geometry.d.ts +15 -0
  233. package/dist/systems/elevator/elevator-geometry.d.ts.map +1 -0
  234. package/dist/systems/elevator/elevator-geometry.js +50 -0
  235. package/dist/systems/elevator/elevator-opening-sync.d.ts +6 -0
  236. package/dist/systems/elevator/elevator-opening-sync.d.ts.map +1 -0
  237. package/dist/systems/elevator/elevator-opening-sync.js +218 -0
  238. package/dist/systems/elevator/elevator-opening-sync.test.d.ts +2 -0
  239. package/dist/systems/elevator/elevator-opening-sync.test.d.ts.map +1 -0
  240. package/dist/systems/elevator/elevator-opening-sync.test.js +146 -0
  241. package/dist/systems/elevator/elevator-opening-system.d.ts +2 -0
  242. package/dist/systems/elevator/elevator-opening-system.d.ts.map +1 -0
  243. package/dist/systems/elevator/elevator-opening-system.js +48 -0
  244. package/dist/systems/elevator/elevator-runtime-system.d.ts +2 -0
  245. package/dist/systems/elevator/elevator-runtime-system.d.ts.map +1 -0
  246. package/dist/systems/elevator/elevator-runtime-system.js +8 -0
  247. package/dist/systems/elevator/elevator-runtime.d.ts +18 -0
  248. package/dist/systems/elevator/elevator-runtime.d.ts.map +1 -0
  249. package/dist/systems/elevator/elevator-runtime.js +225 -0
  250. package/dist/systems/elevator/elevator-runtime.test.d.ts +2 -0
  251. package/dist/systems/elevator/elevator-runtime.test.d.ts.map +1 -0
  252. package/dist/systems/elevator/elevator-runtime.test.js +64 -0
  253. package/dist/systems/elevator/elevator-service.d.ts +19 -0
  254. package/dist/systems/elevator/elevator-service.d.ts.map +1 -0
  255. package/dist/systems/elevator/elevator-service.js +111 -0
  256. package/dist/systems/stair/stair-footprint.d.ts +44 -0
  257. package/dist/systems/stair/stair-footprint.d.ts.map +1 -0
  258. package/dist/systems/stair/stair-footprint.js +168 -0
  259. package/dist/systems/stair/stair-opening-preview.d.ts +1777 -0
  260. package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -0
  261. package/dist/systems/stair/stair-opening-preview.js +93 -0
  262. package/dist/systems/stair/stair-opening-preview.test.d.ts +2 -0
  263. package/dist/systems/stair/stair-opening-preview.test.d.ts.map +1 -0
  264. package/dist/systems/stair/stair-opening-preview.test.js +80 -0
  265. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  266. package/dist/systems/stair/stair-opening-sync.js +183 -185
  267. package/dist/systems/stair/stair-opening-sync.test.js +385 -4
  268. package/dist/systems/stair/stair-opening-system.d.ts +2 -0
  269. package/dist/systems/stair/stair-opening-system.d.ts.map +1 -0
  270. package/dist/systems/stair/stair-opening-system.js +104 -0
  271. package/dist/systems/wall/wall-curve.d.ts +1 -1
  272. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  273. package/dist/systems/wall/wall-curve.js +1 -1
  274. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  275. package/dist/systems/wall/wall-mitering.js +2 -6
  276. package/dist/systems/wall/wall-move.d.ts +48 -0
  277. package/dist/systems/wall/wall-move.d.ts.map +1 -0
  278. package/dist/systems/wall/wall-move.js +193 -0
  279. package/dist/utils/clone-scene-graph.d.ts +6 -4
  280. package/dist/utils/clone-scene-graph.d.ts.map +1 -1
  281. package/dist/utils/clone-scene-graph.js +6 -4
  282. package/dist/utils/clone-scene-graph.test.d.ts +2 -0
  283. package/dist/utils/clone-scene-graph.test.d.ts.map +1 -0
  284. package/dist/utils/clone-scene-graph.test.js +59 -0
  285. package/dist/validation/validate-build-json.d.ts +43 -0
  286. package/dist/validation/validate-build-json.d.ts.map +1 -0
  287. package/dist/validation/validate-build-json.js +227 -0
  288. package/package.json +11 -3
  289. package/dist/materials.d.ts +0 -10
  290. package/dist/materials.d.ts.map +0 -1
  291. package/dist/materials.js +0 -22
  292. package/dist/systems/ceiling/ceiling-system.d.ts +0 -8
  293. package/dist/systems/ceiling/ceiling-system.d.ts.map +0 -1
  294. package/dist/systems/ceiling/ceiling-system.js +0 -92
  295. package/dist/systems/door/door-system.d.ts +0 -2
  296. package/dist/systems/door/door-system.d.ts.map +0 -1
  297. package/dist/systems/door/door-system.js +0 -195
  298. package/dist/systems/fence/fence-system.d.ts +0 -2
  299. package/dist/systems/fence/fence-system.d.ts.map +0 -1
  300. package/dist/systems/fence/fence-system.js +0 -187
  301. package/dist/systems/item/item-system.d.ts +0 -2
  302. package/dist/systems/item/item-system.d.ts.map +0 -1
  303. package/dist/systems/item/item-system.js +0 -48
  304. package/dist/systems/roof/roof-system.d.ts +0 -16
  305. package/dist/systems/roof/roof-system.d.ts.map +0 -1
  306. package/dist/systems/roof/roof-system.js +0 -797
  307. package/dist/systems/slab/slab-system.d.ts +0 -8
  308. package/dist/systems/slab/slab-system.d.ts.map +0 -1
  309. package/dist/systems/slab/slab-system.js +0 -214
  310. package/dist/systems/stair/stair-system.d.ts +0 -2
  311. package/dist/systems/stair/stair-system.d.ts.map +0 -1
  312. package/dist/systems/stair/stair-system.js +0 -776
  313. package/dist/systems/wall/wall-system.d.ts +0 -12
  314. package/dist/systems/wall/wall-system.d.ts.map +0 -1
  315. package/dist/systems/wall/wall-system.js +0 -455
  316. package/dist/systems/window/window-system.d.ts +0 -2
  317. package/dist/systems/window/window-system.d.ts.map +0 -1
  318. package/dist/systems/window/window-system.js +0 -131
@@ -1,4 +1,346 @@
1
- import { getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, } from '../../schema';
1
+ import { AnyNode as AnyNodeSchema, getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, } from '../../schema';
2
+ const NUMBER_FORMAT_BOUNDS = {
3
+ safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
4
+ int32: [-2147483648, 2147483647],
5
+ uint32: [0, 4294967295],
6
+ float32: [-3.4028234663852886e38, 3.4028234663852886e38],
7
+ };
8
+ const INTEGER_NUMBER_FORMATS = new Set(['safeint', 'int32', 'uint32']);
9
+ function getSchemaDef(schema) {
10
+ return schema?._zod?.def ?? schema?.def;
11
+ }
12
+ function getSchemaDefault(schema) {
13
+ const def = getSchemaDef(schema);
14
+ if (!(def?.type === 'default' || def?.type === 'prefault'))
15
+ return undefined;
16
+ return def.defaultValue;
17
+ }
18
+ function unwrapSchema(schema) {
19
+ let current = schema ?? null;
20
+ while (current) {
21
+ const def = getSchemaDef(current);
22
+ if (!(def?.type === 'default' ||
23
+ def?.type === 'prefault' ||
24
+ def?.type === 'optional' ||
25
+ def?.type === 'nullable' ||
26
+ def?.type === 'catch' ||
27
+ def?.type === 'readonly' ||
28
+ def?.type === 'nonoptional')) {
29
+ return current;
30
+ }
31
+ current = def.innerType ?? null;
32
+ }
33
+ return null;
34
+ }
35
+ function getObjectShape(schema) {
36
+ const unwrapped = unwrapSchema(schema);
37
+ const def = getSchemaDef(unwrapped);
38
+ if (def?.type !== 'object')
39
+ return null;
40
+ return unwrapped?.shape ?? def.shape ?? null;
41
+ }
42
+ function schemaAllowsValue(schema, value) {
43
+ const unwrapped = unwrapSchema(schema);
44
+ if (!unwrapped)
45
+ return false;
46
+ const def = getSchemaDef(unwrapped);
47
+ if (unwrapped._zod?.values?.has(value))
48
+ return true;
49
+ if (Array.isArray(def?.values) && def.values.includes(value))
50
+ return true;
51
+ if (def?.entries && Object.values(def.entries).includes(value))
52
+ return true;
53
+ return false;
54
+ }
55
+ function getNodeSchemaForType(type) {
56
+ const schema = AnyNodeSchema;
57
+ const options = getSchemaDef(schema)?.options;
58
+ if (!options)
59
+ return null;
60
+ for (const option of options) {
61
+ const shape = getObjectShape(option);
62
+ if (shape?.type && schemaAllowsValue(shape.type, type)) {
63
+ return option;
64
+ }
65
+ }
66
+ return null;
67
+ }
68
+ function applyLowerLimit(current, candidate) {
69
+ if (!current)
70
+ return candidate;
71
+ if (candidate.value > current.value)
72
+ return candidate;
73
+ if (candidate.value === current.value && !candidate.inclusive)
74
+ return candidate;
75
+ return current;
76
+ }
77
+ function applyUpperLimit(current, candidate) {
78
+ if (!current)
79
+ return candidate;
80
+ if (candidate.value < current.value)
81
+ return candidate;
82
+ if (candidate.value === current.value && !candidate.inclusive)
83
+ return candidate;
84
+ return current;
85
+ }
86
+ function getNumberConstraints(schema) {
87
+ const unwrapped = unwrapSchema(schema) ?? schema;
88
+ const def = getSchemaDef(unwrapped);
89
+ const constraints = { integer: false };
90
+ const minValue = unwrapped.minValue;
91
+ if (typeof minValue === 'number') {
92
+ constraints.min = applyLowerLimit(constraints.min, { value: minValue, inclusive: true });
93
+ }
94
+ const maxValue = unwrapped.maxValue;
95
+ if (typeof maxValue === 'number') {
96
+ constraints.max = applyUpperLimit(constraints.max, { value: maxValue, inclusive: true });
97
+ }
98
+ for (const check of def?.checks ?? []) {
99
+ const checkDef = check._zod?.def;
100
+ if (!checkDef)
101
+ continue;
102
+ if (checkDef.check === 'greater_than' && typeof checkDef.value === 'number') {
103
+ constraints.min = applyLowerLimit(constraints.min, {
104
+ value: checkDef.value,
105
+ inclusive: checkDef.inclusive !== false,
106
+ });
107
+ }
108
+ else if (checkDef.check === 'less_than' && typeof checkDef.value === 'number') {
109
+ constraints.max = applyUpperLimit(constraints.max, {
110
+ value: checkDef.value,
111
+ inclusive: checkDef.inclusive !== false,
112
+ });
113
+ }
114
+ else if (checkDef.check === 'number_format' && checkDef.format) {
115
+ constraints.integer ||= INTEGER_NUMBER_FORMATS.has(checkDef.format);
116
+ const bounds = NUMBER_FORMAT_BOUNDS[checkDef.format];
117
+ if (bounds) {
118
+ constraints.min = applyLowerLimit(constraints.min, {
119
+ value: bounds[0],
120
+ inclusive: true,
121
+ });
122
+ constraints.max = applyUpperLimit(constraints.max, {
123
+ value: bounds[1],
124
+ inclusive: true,
125
+ });
126
+ }
127
+ }
128
+ }
129
+ const bag = unwrapped._zod?.bag;
130
+ if (typeof bag?.minimum === 'number') {
131
+ constraints.min = applyLowerLimit(constraints.min, { value: bag.minimum, inclusive: true });
132
+ }
133
+ if (typeof bag?.exclusiveMinimum === 'number') {
134
+ constraints.min = applyLowerLimit(constraints.min, {
135
+ value: bag.exclusiveMinimum,
136
+ inclusive: false,
137
+ });
138
+ }
139
+ if (typeof bag?.maximum === 'number') {
140
+ constraints.max = applyUpperLimit(constraints.max, { value: bag.maximum, inclusive: true });
141
+ }
142
+ if (typeof bag?.exclusiveMaximum === 'number') {
143
+ constraints.max = applyUpperLimit(constraints.max, {
144
+ value: bag.exclusiveMaximum,
145
+ inclusive: false,
146
+ });
147
+ }
148
+ return constraints;
149
+ }
150
+ function nextAbove(value) {
151
+ return value === 0 ? Number.EPSILON : value + Math.abs(value) * Number.EPSILON;
152
+ }
153
+ function nextBelow(value) {
154
+ return value === 0 ? -Number.EPSILON : value - Math.abs(value) * Number.EPSILON;
155
+ }
156
+ function clampNumber(value, constraints) {
157
+ let next = value;
158
+ let rounded = false;
159
+ let clamped = false;
160
+ if (constraints.integer && !Number.isInteger(next)) {
161
+ next = Math.round(next);
162
+ rounded = true;
163
+ }
164
+ if (constraints.min) {
165
+ const min = constraints.min.inclusive ? constraints.min.value : nextAbove(constraints.min.value);
166
+ if (next < min) {
167
+ next = min;
168
+ clamped = true;
169
+ }
170
+ }
171
+ if (constraints.max) {
172
+ const max = constraints.max.inclusive ? constraints.max.value : nextBelow(constraints.max.value);
173
+ if (next > max) {
174
+ next = max;
175
+ clamped = true;
176
+ }
177
+ }
178
+ return {
179
+ value: next,
180
+ action: clamped ? 'clamped' : rounded ? 'rounded' : undefined,
181
+ };
182
+ }
183
+ function getFiniteFallbackNumber(fallback, constraints) {
184
+ if (typeof fallback !== 'number' || !Number.isFinite(fallback))
185
+ return undefined;
186
+ return clampNumber(fallback, constraints).value;
187
+ }
188
+ function sanitizeNumber(schema, value, fallback, path) {
189
+ const constraints = schema ? getNumberConstraints(schema) : { integer: false };
190
+ if (!Number.isFinite(value)) {
191
+ const replacement = getFiniteFallbackNumber(fallback, constraints);
192
+ if (replacement === undefined) {
193
+ return {
194
+ value,
195
+ omit: true,
196
+ issues: [{ path, from: value, action: 'dropped' }],
197
+ };
198
+ }
199
+ return {
200
+ value: replacement,
201
+ issues: [{ path, from: value, to: replacement, action: 'dropped' }],
202
+ };
203
+ }
204
+ const clamped = clampNumber(value, constraints);
205
+ if (!Object.is(clamped.value, value)) {
206
+ return {
207
+ value: clamped.value,
208
+ issues: [
209
+ {
210
+ path,
211
+ from: value,
212
+ to: clamped.value,
213
+ action: clamped.action ?? 'clamped',
214
+ },
215
+ ],
216
+ };
217
+ }
218
+ return { value, issues: [] };
219
+ }
220
+ function sanitizeNumericValue(schema, value, fallback, path) {
221
+ const defaultFallback = schema ? getSchemaDefault(schema) : undefined;
222
+ const effectiveFallback = fallback === undefined ? defaultFallback : fallback;
223
+ const unwrapped = unwrapSchema(schema);
224
+ const def = getSchemaDef(unwrapped);
225
+ if (def?.type === 'number') {
226
+ if (typeof value !== 'number')
227
+ return { value, issues: [] };
228
+ return sanitizeNumber(unwrapped, value, effectiveFallback, path);
229
+ }
230
+ if (typeof value === 'number') {
231
+ return sanitizeNumber(null, value, effectiveFallback, path);
232
+ }
233
+ if (def?.type === 'tuple' && Array.isArray(value)) {
234
+ const fallbackItems = Array.isArray(effectiveFallback) ? effectiveFallback : [];
235
+ const next = [...value];
236
+ const issues = [];
237
+ for (let index = 0; index < next.length; index += 1) {
238
+ const itemSchema = def.items?.[index] ?? def.rest ?? null;
239
+ const child = sanitizeNumericValue(itemSchema, next[index], fallbackItems[index], [
240
+ ...path,
241
+ index,
242
+ ]);
243
+ issues.push(...child.issues);
244
+ if (child.omit) {
245
+ return { value, omit: true, issues };
246
+ }
247
+ next[index] = child.value;
248
+ }
249
+ return { value: issues.length > 0 ? next : value, issues };
250
+ }
251
+ if (def?.type === 'array' && Array.isArray(value)) {
252
+ const fallbackItems = Array.isArray(effectiveFallback) ? effectiveFallback : [];
253
+ const next = [];
254
+ const issues = [];
255
+ let omitted = false;
256
+ for (let index = 0; index < value.length; index += 1) {
257
+ const child = sanitizeNumericValue(def.element ?? null, value[index], fallbackItems[index], [
258
+ ...path,
259
+ index,
260
+ ]);
261
+ issues.push(...child.issues);
262
+ if (child.omit) {
263
+ omitted = true;
264
+ continue;
265
+ }
266
+ next.push(child.value);
267
+ }
268
+ return { value: issues.length > 0 || omitted ? next : value, issues };
269
+ }
270
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
271
+ const shape = def?.type === 'object' ? (unwrapped?.shape ?? def.shape ?? {}) : {};
272
+ const fallbackObject = effectiveFallback &&
273
+ typeof effectiveFallback === 'object' &&
274
+ !Array.isArray(effectiveFallback)
275
+ ? effectiveFallback
276
+ : {};
277
+ const input = value;
278
+ const next = { ...input };
279
+ const issues = [];
280
+ for (const key of Object.keys(input)) {
281
+ const child = sanitizeNumericValue(shape[key] ?? null, input[key], fallbackObject[key], [
282
+ ...path,
283
+ key,
284
+ ]);
285
+ issues.push(...child.issues);
286
+ if (child.omit) {
287
+ delete next[key];
288
+ }
289
+ else {
290
+ next[key] = child.value;
291
+ }
292
+ }
293
+ return { value: issues.length > 0 ? next : value, issues };
294
+ }
295
+ return { value, issues: [] };
296
+ }
297
+ function formatNumericValue(value) {
298
+ if (Number.isNaN(value))
299
+ return 'NaN';
300
+ if (value === Infinity)
301
+ return 'Infinity';
302
+ if (value === -Infinity)
303
+ return '-Infinity';
304
+ return String(value);
305
+ }
306
+ function numericSanitizeIssuesToMessage(issues) {
307
+ return issues
308
+ .map((issue) => {
309
+ const path = issue.path.map(String).join('.') || '<root>';
310
+ const to = issue.to === undefined ? '' : ` -> ${formatNumericValue(issue.to)}`;
311
+ return `${path}: ${formatNumericValue(issue.from)} ${issue.action}${to}`;
312
+ })
313
+ .join('; ');
314
+ }
315
+ function warnSanitizedNodeMutation(mutation, nodeId, issues) {
316
+ console.warn(`[Scene] Sanitized invalid numeric node ${mutation}`, nodeId, numericSanitizeIssuesToMessage(issues));
317
+ }
318
+ function parseCreatedNode(node, parentId) {
319
+ const candidate = { ...node, parentId };
320
+ const parsed = AnyNodeSchema.safeParse(candidate);
321
+ if (parsed.success)
322
+ return parsed.data;
323
+ const schema = getNodeSchemaForType(candidate.type);
324
+ const sanitized = sanitizeNumericValue(schema, candidate, undefined, []);
325
+ if (sanitized.issues.length === 0) {
326
+ return candidate;
327
+ }
328
+ warnSanitizedNodeMutation('create', node.id, sanitized.issues);
329
+ return sanitized.value;
330
+ }
331
+ function parseUpdatedNode(currentNode, data) {
332
+ const candidate = { ...currentNode, ...data };
333
+ const parsed = AnyNodeSchema.safeParse(candidate);
334
+ if (parsed.success)
335
+ return parsed.data;
336
+ const schema = getNodeSchemaForType(candidate.type);
337
+ const sanitized = sanitizeNumericValue(schema, data, currentNode, []);
338
+ if (sanitized.issues.length === 0) {
339
+ return candidate;
340
+ }
341
+ warnSanitizedNodeMutation('update', currentNode.id, sanitized.issues);
342
+ return { ...currentNode, ...sanitized.value };
343
+ }
2
344
  // Track pending RAF for updateNodesAction to prevent multiple queued callbacks
3
345
  let pendingRafId = null;
4
346
  let pendingUpdates = new Set();
@@ -72,7 +414,7 @@ function buildMergedWallAttachmentUpdates(primary, secondary, mergedWallId, merg
72
414
  const wallChildren = [...(primary.children ?? []), ...(secondary.children ?? [])];
73
415
  for (const childId of wallChildren) {
74
416
  const child = nodes[childId];
75
- if (!child || !('position' in child) || !Array.isArray(child.position)) {
417
+ if (!(child && 'position' in child && Array.isArray(child.position))) {
76
418
  continue;
77
419
  }
78
420
  const sourceWall = child.parentId === secondary.id ? secondary : primary;
@@ -123,10 +465,10 @@ function buildWallMergePlans(nodes, idsToDelete) {
123
465
  return a.id.localeCompare(b.id);
124
466
  });
125
467
  const [primary, secondary] = sortedCandidates;
126
- if (!primary ||
127
- !secondary ||
128
- !areWallStylesCompatible(primary, secondary) ||
129
- !areWallsCollinearAcrossPoint(primary, secondary, junction)) {
468
+ if (!(primary &&
469
+ secondary &&
470
+ areWallStylesCompatible(primary, secondary) &&
471
+ areWallsCollinearAcrossPoint(primary, secondary, junction))) {
130
472
  continue;
131
473
  }
132
474
  const { start, end } = resolveMergedWallEndpoints(primary, secondary, junction);
@@ -154,21 +496,25 @@ export const createNodesAction = (set, get, ops) => {
154
496
  const nextRootIds = [...state.rootNodeIds];
155
497
  for (const { node, parentId } of ops) {
156
498
  const effectiveParentId = parentId ?? node.parentId ?? null;
157
- // 1. Assign parentId to the child (Safe because BaseNode has parentId)
158
- const newNode = {
159
- ...node,
160
- parentId: effectiveParentId,
161
- };
499
+ const newNode = parseCreatedNode(node, effectiveParentId);
162
500
  nextNodes[newNode.id] = newNode;
163
- // 2. Update the Parent's children list
501
+ // 2. Update the Parent's children list. We append to ANY container
502
+ // parent (kind has `children` in its schema) — if the field is
503
+ // present but undefined (e.g. an old saved scene from before the
504
+ // kind gained children), we initialise to `[]` first so the
505
+ // reparenting goes through. Without this, hosting items on an
506
+ // old shelf (v1, before `children` was added) silently no-ops:
507
+ // the item is reparented to the shelf but the shelf's children
508
+ // array is never updated, so `ParametricNodeRenderer` doesn't
509
+ // mount it and the item "disappears".
164
510
  if (effectiveParentId && nextNodes[effectiveParentId]) {
165
511
  const parent = nextNodes[effectiveParentId];
166
- // Type Guard: Check if the parent node is a container that supports children
167
- if ('children' in parent && Array.isArray(parent.children)) {
512
+ if ('children' in parent) {
513
+ const existing = parent.children;
514
+ const children = Array.isArray(existing) ? existing : [];
168
515
  nextNodes[effectiveParentId] = {
169
516
  ...parent,
170
- // Use Set to prevent duplicate IDs if createNode is called twice
171
- children: Array.from(new Set([...parent.children, newNode.id])), // We don't verify child types here
517
+ children: Array.from(new Set([...children, newNode.id])),
172
518
  };
173
519
  }
174
520
  }
@@ -186,8 +532,129 @@ export const createNodesAction = (set, get, ops) => {
186
532
  get().markDirty(node.id);
187
533
  if (parentId)
188
534
  get().markDirty(parentId);
535
+ else if (node.parentId)
536
+ get().markDirty(node.parentId);
189
537
  });
190
538
  };
539
+ export const applyNodeChangesAction = (set, get, changes) => {
540
+ if (get().readOnly)
541
+ return;
542
+ const createOps = changes.create ?? [];
543
+ const updateOps = changes.update ?? [];
544
+ const deleteOps = changes.delete ?? [];
545
+ const nodesToMarkDirty = new Set();
546
+ const parentsToMarkDirty = new Set();
547
+ set((state) => {
548
+ const nextNodes = { ...state.nodes };
549
+ const nextCollections = { ...state.collections };
550
+ const nextRootIds = [...state.rootNodeIds];
551
+ let resolvedRootIds = nextRootIds;
552
+ for (const { id, data } of updateOps) {
553
+ const currentNode = nextNodes[id];
554
+ if (!currentNode)
555
+ continue;
556
+ const updatedNode = parseUpdatedNode(currentNode, data);
557
+ if (data.parentId !== undefined && data.parentId !== currentNode.parentId) {
558
+ const oldParentId = currentNode.parentId;
559
+ if (oldParentId && nextNodes[oldParentId]) {
560
+ const oldParent = nextNodes[oldParentId];
561
+ nextNodes[oldParent.id] = {
562
+ ...oldParent,
563
+ children: oldParent.children.filter((childId) => childId !== id),
564
+ };
565
+ parentsToMarkDirty.add(oldParent.id);
566
+ }
567
+ const newParentId = data.parentId;
568
+ if (newParentId && nextNodes[newParentId]) {
569
+ const newParent = nextNodes[newParentId];
570
+ nextNodes[newParent.id] = {
571
+ ...newParent,
572
+ children: Array.from(new Set([...newParent.children, id])),
573
+ };
574
+ parentsToMarkDirty.add(newParent.id);
575
+ }
576
+ }
577
+ nextNodes[id] = updatedNode;
578
+ nodesToMarkDirty.add(id);
579
+ }
580
+ for (const { node, parentId } of createOps) {
581
+ const effectiveParentId = parentId ?? node.parentId ?? null;
582
+ const newNode = parseCreatedNode(node, effectiveParentId);
583
+ nextNodes[newNode.id] = newNode;
584
+ nodesToMarkDirty.add(newNode.id);
585
+ if (effectiveParentId && nextNodes[effectiveParentId]) {
586
+ const parent = nextNodes[effectiveParentId];
587
+ if ('children' in parent && Array.isArray(parent.children)) {
588
+ nextNodes[effectiveParentId] = {
589
+ ...parent,
590
+ children: Array.from(new Set([...parent.children, newNode.id])),
591
+ };
592
+ parentsToMarkDirty.add(effectiveParentId);
593
+ }
594
+ }
595
+ else if (!effectiveParentId && !nextRootIds.includes(newNode.id)) {
596
+ nextRootIds.push(newNode.id);
597
+ }
598
+ }
599
+ const allIdsToDelete = new Set();
600
+ const collectDelete = (id) => {
601
+ if (allIdsToDelete.has(id))
602
+ return;
603
+ allIdsToDelete.add(id);
604
+ const node = nextNodes[id];
605
+ if (node && 'children' in node && Array.isArray(node.children)) {
606
+ for (const childId of node.children) {
607
+ collectDelete(childId);
608
+ }
609
+ }
610
+ };
611
+ for (const id of deleteOps) {
612
+ collectDelete(id);
613
+ }
614
+ for (const id of allIdsToDelete) {
615
+ const node = nextNodes[id];
616
+ if (!node)
617
+ continue;
618
+ const parentId = node.parentId;
619
+ if (parentId && nextNodes[parentId] && !allIdsToDelete.has(parentId)) {
620
+ const parent = nextNodes[parentId];
621
+ if (parent.children) {
622
+ nextNodes[parent.id] = {
623
+ ...parent,
624
+ children: parent.children.filter((childId) => childId !== id),
625
+ };
626
+ parentsToMarkDirty.add(parent.id);
627
+ }
628
+ }
629
+ resolvedRootIds = resolvedRootIds.filter((rootId) => rootId !== id);
630
+ if ('collectionIds' in node && node.collectionIds) {
631
+ for (const collectionId of node.collectionIds) {
632
+ const collection = nextCollections[collectionId];
633
+ if (collection) {
634
+ nextCollections[collectionId] = {
635
+ ...collection,
636
+ nodeIds: collection.nodeIds.filter((nodeId) => nodeId !== id),
637
+ };
638
+ }
639
+ }
640
+ }
641
+ delete nextNodes[id];
642
+ }
643
+ return { nodes: nextNodes, rootNodeIds: resolvedRootIds, collections: nextCollections };
644
+ });
645
+ for (const id of nodesToMarkDirty) {
646
+ get().markDirty(id);
647
+ }
648
+ for (const id of parentsToMarkDirty) {
649
+ get().markDirty(id);
650
+ const parent = get().nodes[id];
651
+ if (parent && 'children' in parent && Array.isArray(parent.children)) {
652
+ for (const childId of parent.children) {
653
+ get().markDirty(childId);
654
+ }
655
+ }
656
+ }
657
+ };
191
658
  export const updateNodesAction = (set, get, updates) => {
192
659
  if (get().readOnly)
193
660
  return;
@@ -198,31 +665,43 @@ export const updateNodesAction = (set, get, updates) => {
198
665
  const currentNode = nextNodes[id];
199
666
  if (!currentNode)
200
667
  continue;
668
+ const updatedNode = parseUpdatedNode(currentNode, data);
201
669
  // Handle Reparenting Logic
202
670
  if (data.parentId !== undefined && data.parentId !== currentNode.parentId) {
203
671
  // 1. Remove from old parent
204
672
  const oldParentId = currentNode.parentId;
205
673
  if (oldParentId && nextNodes[oldParentId]) {
206
674
  const oldParent = nextNodes[oldParentId];
675
+ const oldChildren = Array.isArray(oldParent.children)
676
+ ? oldParent.children
677
+ : [];
207
678
  nextNodes[oldParent.id] = {
208
679
  ...oldParent,
209
- children: oldParent.children.filter((childId) => childId !== id),
680
+ children: oldChildren.filter((childId) => childId !== id),
210
681
  };
211
682
  parentsToUpdate.add(oldParent.id);
212
683
  }
213
- // 2. Add to new parent
684
+ // 2. Add to new parent. Defensive against parents that don't yet
685
+ // carry a `children` array — older saved scenes can predate the
686
+ // schema field on a particular kind (shelf v1 → v2 added one),
687
+ // and a spread of `undefined` here throws and aborts the entire
688
+ // `set` callback. Initialising to `[]` matches what the schema's
689
+ // default would have produced.
214
690
  const newParentId = data.parentId;
215
691
  if (newParentId && nextNodes[newParentId]) {
216
692
  const newParent = nextNodes[newParentId];
693
+ const newChildren = Array.isArray(newParent.children)
694
+ ? newParent.children
695
+ : [];
217
696
  nextNodes[newParent.id] = {
218
697
  ...newParent,
219
- children: Array.from(new Set([...newParent.children, id])),
698
+ children: Array.from(new Set([...newChildren, id])),
220
699
  };
221
700
  parentsToUpdate.add(newParent.id);
222
701
  }
223
702
  }
224
703
  // Apply the update
225
- nextNodes[id] = { ...nextNodes[id], ...data };
704
+ nextNodes[id] = updatedNode;
226
705
  }
227
706
  return { nodes: nextNodes };
228
707
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=node-mutation-sanitize.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-mutation-sanitize.test.d.ts","sourceRoot":"","sources":["../../../src/store/actions/node-mutation-sanitize.test.ts"],"names":[],"mappings":""}