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

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.
@@ -1080,22 +1080,24 @@ const useScene = create()(temporal((set, get) => ({
1080
1080
  });
1081
1081
  return; // Scene already loaded
1082
1082
  }
1083
- // Create hierarchy: Site → Building → Level
1083
+ // Create hierarchy: Site → Building → Level. Parent links must be
1084
+ // written explicitly — the schema defaults `parentId` to null, and the
1085
+ // scene authority rejects parent/child asymmetry that the renderer
1086
+ // happily traverses through `children`.
1087
+ const site = SiteNode.parse({});
1088
+ const building = BuildingNode.parse({
1089
+ parentId: site.id,
1090
+ });
1084
1091
  const level0 = LevelNode.parse({
1092
+ parentId: building.id,
1085
1093
  level: 0,
1086
1094
  children: [],
1087
1095
  height: 2.5,
1088
1096
  });
1089
- const building = BuildingNode.parse({
1090
- children: [level0.id],
1091
- });
1092
- const site = SiteNode.parse({
1093
- children: [building.id],
1094
- });
1095
1097
  // Define all nodes flat
1096
1098
  const nodes = {
1097
- [site.id]: site,
1098
- [building.id]: building,
1099
+ [site.id]: { ...site, children: [building.id] },
1100
+ [building.id]: { ...building, children: [level0.id] },
1099
1101
  [level0.id]: level0,
1100
1102
  };
1101
1103
  // Site is the root
@@ -9,6 +9,11 @@ export interface HealSceneResult {
9
9
  * a different node (stale reparent leftovers), plus same-array duplicates.
10
10
  */
11
11
  strippedStaleChildRefs: number;
12
+ /**
13
+ * Ids of nodes whose null `parentId` was repaired to the one parent that
14
+ * still claims them via `children`.
15
+ */
16
+ repairedParentLinkNodeIds: string[];
12
17
  }
13
18
  /**
14
19
  * Returns a healed copy of a `nodes` map. Pure — does not mutate `input`.
@@ -1 +1 @@
1
- {"version":3,"file":"heal-scene-graph.d.ts","sourceRoot":"","sources":["../../src/utils/heal-scene-graph.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,kDAAkD;IAClD,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,sFAAsF;IACtF,iBAAiB,EAAE,MAAM,CAAA;IACzB;;;OAGG;IACH,sBAAsB,EAAE,MAAM,CAAA;CAC/B;AAgBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CA2E9E"}
1
+ {"version":3,"file":"heal-scene-graph.d.ts","sourceRoot":"","sources":["../../src/utils/heal-scene-graph.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,kDAAkD;IAClD,cAAc,EAAE,MAAM,EAAE,CAAA;IACxB,sFAAsF;IACtF,iBAAiB,EAAE,MAAM,CAAA;IACzB;;;OAGG;IACH,sBAAsB,EAAE,MAAM,CAAA;IAC9B;;;OAGG;IACH,yBAAyB,EAAE,MAAM,EAAE,CAAA;CACpC;AAgBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CAiH9E"}
@@ -13,6 +13,11 @@
13
13
  // duplicate reference renders the child twice (duplicate React keys in the
14
14
  // 2D plan, doubled hosted geometry in 3D). Same-array duplicates are
15
15
  // collapsed too.
16
+ // 4. A node with a null `parentId` that exactly one parent still claims via
17
+ // `children` (the legacy site-child flatten and the old default-scene
18
+ // assembler both linked children without writing `parentId`). The editor
19
+ // renders the chain through `children`, but the hosted scene authority
20
+ // validates parent/child symmetry and rejects the whole scene.
16
21
  //
17
22
  // All are also prevented at the source now; this is the load-time safety net
18
23
  // for already-saved scenes.
@@ -102,5 +107,45 @@ export function healSceneNodes(input) {
102
107
  }
103
108
  nodes[id] = node;
104
109
  }
105
- return { nodes, droppedWallIds, strippedChildRefs, strippedStaleChildRefs };
110
+ // Pass 3: repair null parent links. A node claimed as a child by exactly one
111
+ // parent must point back at it; legacy writers linked `children` without
112
+ // writing `parentId`. Embedded legacy site children claim by their `id` so
113
+ // the flattened flat-map node is repaired too.
114
+ const claimantsByChildId = new Map();
115
+ for (const [id, node] of Object.entries(nodes)) {
116
+ const children = node?.children;
117
+ if (!Array.isArray(children))
118
+ continue;
119
+ for (const child of children) {
120
+ const childId = typeof child === 'string'
121
+ ? child
122
+ : child && typeof child === 'object' && typeof child.id === 'string'
123
+ ? child.id
124
+ : null;
125
+ if (!childId || !(childId in nodes))
126
+ continue;
127
+ const claimants = claimantsByChildId.get(childId) ?? [];
128
+ claimants.push(id);
129
+ claimantsByChildId.set(childId, claimants);
130
+ }
131
+ }
132
+ const repairedParentLinkNodeIds = [];
133
+ for (const [id, node] of Object.entries(nodes)) {
134
+ if (!node || typeof node !== 'object')
135
+ continue;
136
+ if (node.parentId != null)
137
+ continue;
138
+ const claimants = claimantsByChildId.get(id);
139
+ if (claimants?.length !== 1 || claimants[0] === id)
140
+ continue;
141
+ nodes[id] = { ...node, parentId: claimants[0] };
142
+ repairedParentLinkNodeIds.push(id);
143
+ }
144
+ return {
145
+ nodes,
146
+ droppedWallIds,
147
+ strippedChildRefs,
148
+ strippedStaleChildRefs,
149
+ repairedParentLinkNodeIds,
150
+ };
106
151
  }
@@ -0,0 +1,3 @@
1
+ export { type HealSceneResult, healSceneNodes } from './heal-scene-graph';
2
+ export { type RetiredSceneNodeMigration, removeRetiredDrawingSheetNodes, } from './retired-scene-nodes';
3
+ //# sourceMappingURL=scene-migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-migrations.d.ts","sourceRoot":"","sources":["../../src/utils/scene-migrations.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACzE,OAAO,EACL,KAAK,yBAAyB,EAC9B,8BAA8B,GAC/B,MAAM,uBAAuB,CAAA"}
@@ -0,0 +1,6 @@
1
+ // Server-safe scene migrations shared by the client loader and the hosted
2
+ // scene authority. Everything exported here must stay pure data logic with no
3
+ // store, React, or Three.js imports so it can run in Server Components and
4
+ // API routes.
5
+ export { healSceneNodes } from './heal-scene-graph';
6
+ export { removeRetiredDrawingSheetNodes, } from './retired-scene-nodes';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/core",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "Core library for Pascal 3D building editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,9 +17,9 @@
17
17
  "default": "./dist/utils/clone-scene-graph.js"
18
18
  },
19
19
  "./scene-migrations": {
20
- "types": "./dist/utils/retired-scene-nodes.d.ts",
21
- "import": "./dist/utils/retired-scene-nodes.js",
22
- "default": "./dist/utils/retired-scene-nodes.js"
20
+ "types": "./dist/utils/scene-migrations.d.ts",
21
+ "import": "./dist/utils/scene-migrations.js",
22
+ "default": "./dist/utils/scene-migrations.js"
23
23
  },
24
24
  "./registry": {
25
25
  "types": "./dist/registry/index.d.ts",