Skip to content

Commit 74888d5

Browse files
nieaoclaude
andcommitted
fix(canvas): 孤儿节点改挂隐形 fallback root, 不再丢失节点
v1 清 parentNode 导致容器内坐标变绝对, 全堆左上 (图 41) v2 filter 孤儿 → PDF 等顶层节点偶尔被误判 parent 也消失 (图 42) v3 (当前) 给所有孤儿挂一个 8000x8000 透明 fallback group: - 不丢节点 (用户图 42 报告 PDF 不见) - 不堆叠 (子节点用原相对坐标在 fallback 内渲染) - 不阻挡交互 (pointerEvents: none, 透明) - yjs 把真实 parent 同步过来后 remap 自动恢复 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 291ad64 commit 74888d5

1 file changed

Lines changed: 30 additions & 11 deletions

File tree

src/components/canvas/KnowledgeCanvas.jsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -282,23 +282,42 @@ function KnowledgeCanvasInner({
282282
children,
283283
}) {
284284
// === 渲染前 sanitize: 检测 parentNode 引用不存在的节点 ===
285-
// React Flow 11 在 child.parentNode 找不到时会直接 throw "Parent node X not found"
286-
// 整个画布崩 (用户报告 ErrorBoundary, 图 37)。这里清掉 dangling parentNode,
287-
// 让孤儿子节点退化成普通根节点继续渲染 — 协作场景下 yjs 同步顺序 / undo 都可能造成短暂不一致。
285+
// React Flow 11 在 child.parentNode 找不到时直接 throw "Parent node X not found"
286+
// 整个画布崩 (图 37)。
287+
//
288+
// 历经两版 trade-off:
289+
// v1 清 parentNode → 容器内坐标变绝对, 全堆左上 (图 41)
290+
// v2 filter 孤儿 → PDF 等顶层节点偶尔被误判 parent 也消失 (图 42)
291+
// v3 (当前) 给所有孤儿挂一个隐形 fallback group, 不丢节点也不堆叠
292+
const ORPHAN_FALLBACK_ID = '__orphan-fallback-root__'
288293
const nodes = useMemo(() => {
289294
const idSet = new Set(rawNodes.map((n) => n.id))
290-
let dirty = false
291-
const cleaned = rawNodes.map((n) => {
295+
let hasOrphan = false
296+
const remapped = rawNodes.map((n) => {
292297
if (n.parentNode && !idSet.has(n.parentNode)) {
293-
dirty = true
294-
// eslint-disable-next-line no-unused-vars
295-
const { parentNode, extent, ...rest } = n
296-
return rest
298+
hasOrphan = true
299+
return { ...n, parentNode: ORPHAN_FALLBACK_ID, extent: undefined }
297300
}
298301
return n
299302
})
300-
if (dirty) console.warn('[KnowledgeCanvas] 清理了孤儿 parentNode 引用, count =', cleaned.length)
301-
return cleaned
303+
if (hasOrphan) {
304+
// 透明大 group, 不显示边框 / 背景, 不阻挡交互, 让子节点用原相对坐标继续渲染
305+
remapped.unshift({
306+
id: ORPHAN_FALLBACK_ID,
307+
type: 'group',
308+
position: { x: 0, y: 0 },
309+
style: {
310+
width: 8000,
311+
height: 8000,
312+
background: 'transparent',
313+
border: 'none',
314+
pointerEvents: 'none',
315+
},
316+
data: { isOrphanFallback: true },
317+
})
318+
console.warn('[KnowledgeCanvas] 检测到孤儿子节点, 已挂 fallback root, 等 yjs sync 后实际 parent 到位会自动接管')
319+
}
320+
return remapped
302321
}, [rawNodes])
303322

304323
const reactFlowInstance = useReactFlow()

0 commit comments

Comments
 (0)