Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fb7d46d

Browse files
authored
Merge pull request #325 from jelharou/fix/site-node-children-string-ids
fix: update mcp and nodes for SiteNode.children string[] schema change
2 parents 6ea622a + 01639e4 commit fb7d46d

3 files changed

Lines changed: 9 additions & 76 deletions

File tree

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,12 @@
11
import type { SceneGraph } from '@pascal-app/core/clone-scene-graph'
2-
import type { AnyNode } from '@pascal-app/core/schema'
32

43
/**
5-
* `cloneSceneGraph` normalises `SiteNode.children` to an array of node IDs,
6-
* but core's `SiteNode` schema expects an array of embedded `BuildingNode` /
7-
* `ItemNode` objects (see `packages/mcp/CROSS_CUTTING.md` §2). To keep the
8-
* cloned graph validating against `AnyNode`, re-embed the site children from
9-
* the flat dict.
10-
*
11-
* Pure: returns a new graph without mutating the input.
4+
* Previously re-embedded `SiteNode.children` from flat IDs back to full node
5+
* objects to match the old schema. Since `SiteNode.children` is now
6+
* `string[]` (upstream change), `cloneSceneGraph` / `forkSceneGraph` already
7+
* produce the correct form — this function is a no-op kept for call-site
8+
* compatibility.
129
*/
1310
export function rehydrateSiteChildren(graph: SceneGraph): SceneGraph {
14-
const out: SceneGraph = {
15-
nodes: { ...graph.nodes },
16-
rootNodeIds: [...graph.rootNodeIds],
17-
...(graph.collections ? { collections: graph.collections } : {}),
18-
}
19-
for (const [id, node] of Object.entries(out.nodes)) {
20-
if (node.type !== 'site') continue
21-
const childrenField = (node as { children?: unknown[] }).children
22-
if (!Array.isArray(childrenField)) continue
23-
const rehydrated: AnyNode[] = []
24-
for (const child of childrenField) {
25-
if (typeof child === 'string') {
26-
const target = out.nodes[child as keyof typeof out.nodes]
27-
if (target && (target.type === 'building' || target.type === 'item')) {
28-
rehydrated.push(target)
29-
}
30-
} else if (child && typeof child === 'object' && 'id' in (child as Record<string, unknown>)) {
31-
rehydrated.push(child as AnyNode)
32-
}
33-
}
34-
out.nodes[id as keyof typeof out.nodes] = {
35-
...(node as AnyNode),
36-
children: rehydrated,
37-
} as AnyNode
38-
}
39-
return out
11+
return graph
4012
}

packages/mcp/src/tools/variants/generate-variants.ts

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
22
import { forkSceneGraph, type SceneGraph } from '@pascal-app/core/clone-scene-graph'
3-
import { type AnyNode, AnyNode as AnyNodeSchema } from '@pascal-app/core/schema'
3+
import { AnyNode as AnyNodeSchema } from '@pascal-app/core/schema'
44
import { z } from 'zod'
55
import type { SceneOperations } from '../../operations'
66
import { ErrorCode, throwMcpError } from '../errors'
@@ -43,43 +43,6 @@ export const generateVariantsOutput = {
4343
),
4444
}
4545

46-
/**
47-
* `forkSceneGraph` normalises `SiteNode.children` to string IDs, but the
48-
* `SiteNode` schema declares that field as an array of full `BuildingNode` /
49-
* `ItemNode` objects (see CROSS_CUTTING §2). To keep variants validating
50-
* against `AnyNode`, re-embed the site children from the flat dict.
51-
*
52-
* Pure: returns a new graph without mutating the input.
53-
*/
54-
function rehydrateSiteChildren(graph: SceneGraph): SceneGraph {
55-
const out: SceneGraph = {
56-
nodes: { ...graph.nodes },
57-
rootNodeIds: [...graph.rootNodeIds],
58-
...(graph.collections ? { collections: graph.collections } : {}),
59-
}
60-
for (const [id, node] of Object.entries(out.nodes)) {
61-
if (node.type !== 'site') continue
62-
const childrenField = (node as { children?: unknown[] }).children
63-
if (!Array.isArray(childrenField)) continue
64-
const rehydrated: AnyNode[] = []
65-
for (const child of childrenField) {
66-
if (typeof child === 'string') {
67-
const target = out.nodes[child as keyof typeof out.nodes]
68-
if (target && (target.type === 'building' || target.type === 'item')) {
69-
rehydrated.push(target)
70-
}
71-
} else if (child && typeof child === 'object' && 'id' in (child as Record<string, unknown>)) {
72-
rehydrated.push(child as AnyNode)
73-
}
74-
}
75-
out.nodes[id as keyof typeof out.nodes] = {
76-
...(node as AnyNode),
77-
children: rehydrated,
78-
} as AnyNode
79-
}
80-
return out
81-
}
82-
8346
/**
8447
* Count how many nodes in a graph fail `AnyNode` validation. Used to keep the
8548
* tool from returning silently corrupt variants.
@@ -140,8 +103,6 @@ export function registerGenerateVariants(server: McpServer, bridge: SceneOperati
140103
for (const kind of mutations) {
141104
forked = applyMutation(forked, rng, kind)
142105
}
143-
// Re-embed site children so variants match the SiteNode schema.
144-
forked = rehydrateSiteChildren(forked)
145106

146107
const invalidCount = countInvalidNodes(forked)
147108
if (invalidCount > 0) {

packages/nodes/src/site/renderer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { type SiteNode, type SlabNode, useRegistry, useScene } from '@pascal-app/core'
3+
import { type AnyNodeId, type SiteNode, type SlabNode, useRegistry, useScene } from '@pascal-app/core'
44
import { NodeRenderer, unionPolygons, useNodeEvents, useViewer } from '@pascal-app/viewer'
55
import { useMemo, useRef } from 'react'
66
import { BufferGeometry, Float32BufferAttribute, type Group, Path, Shape } from 'three'
@@ -117,7 +117,7 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => {
117117
<group ref={ref} {...handlers}>
118118
{/* Render children (buildings and items) */}
119119
{node.children.map((childId) => (
120-
<NodeRenderer key={childId} nodeId={childId} />
120+
<NodeRenderer key={childId} nodeId={childId as AnyNodeId} />
121121
))}
122122

123123
{/* Ground fill: site polygon with slab holes, occludes below-grade geometry */}

0 commit comments

Comments
 (0)