name: dev-debug-canvas-nested-groups description: This skill should be used when debugging Vue Flow canvas issues involving nested nodes, parent-child relationships, position coordinate systems, and containment detection. Triggers on nested group problems, section nodes jumping, parent-child synchronization bugs, position persistence issues, and coordinate transformation errors in Vue Flow implementations.
Vue Flow Nested Nodes Debugging Skill
Overview
Debug Vue Flow nested nodes, parent-child relationships, position coordinate systems, and containment detection issues. This skill provides systematic diagnostic procedures, code patterns, and utilities for troubleshooting complex canvas hierarchy problems.
When to Use This Skill
Use this skill when encountering:
- Nested groups not moving together when parent is dragged
- Section nodes jumping to unexpected positions
- Parent-child relationships not being recognized
- Tasks inside groups not being detected correctly
- Position values appearing incorrect after operations
- Groups resetting or losing their nested structure
- Coordinate mismatches between UI and stored data
Quick Diagnostic
Before deep debugging, run these quick checks:
// 1. Check if parentNode is properly set
const node = getNode('task-123')
console.log('Parent:', node?.parentNode) // Should be group ID or undefined
// 2. Verify coordinate types
console.log('Position (relative):', node?.position)
console.log('ComputedPosition (absolute):', node?.computedPosition)
// 3. Check extent mode
console.log('Extent:', node?.extent) // 'parent' for constrained children
Core Concept: Coordinate Systems
CRITICAL: Vue Flow uses TWO coordinate systems that must not be confused:
| Property | Type | When Used |
|---|---|---|
node.position | Relative to parent | Child nodes with parentNode set |
node.position | Absolute canvas coords | Root nodes (no parent) |
node.computedPosition | Always absolute | Use for canvas operations |
Golden Rule: Store relative positions for children, use computedPosition for calculations.
Debugging Workflow
Step 1: Identify the Problem Type
Consult the troubleshooting tree in references/troubleshooting-tree.md:
- Position Issues: Node jumping, incorrect placement, reset positions
- Hierarchy Issues: Parent-child not recognized, nesting lost
- Movement Issues: Children not moving with parent, group drag problems
- Containment Issues: Tasks not detected inside groups
Step 2: Enable Debug Logging
Add targeted logging based on problem type:
// For position debugging
watch(() => node.position, (newPos, oldPos) => {
console.log(`[POSITION] ${node.id}: ${JSON.stringify(oldPos)} → ${JSON.stringify(newPos)}`)
}, { deep: true })
// For hierarchy debugging
watch(() => node.parentNode, (newParent, oldParent) => {
console.log(`[HIERARCHY] ${node.id}: parent ${oldParent} → ${newParent}`)
})
Step 3: Use Diagnostic Utilities
Load the debugging utilities from scripts/vueFlowDebug.ts:
import { logNodeHierarchy, validateParentChild, dumpCanvasState } from './vueFlowDebug'
// Dump full canvas state
dumpCanvasState(getNodes.value, getEdges.value)
// Validate all parent-child relationships
validateParentChild(getNodes.value)
// Log hierarchy tree
logNodeHierarchy(getNodes.value)
Step 4: Apply Fix Patterns
Consult references/code-patterns.md for proven solutions:
- Reparenting: Correct way to move node between parents
- Position Conversion: Converting between coordinate systems
- Containment Detection: Proper center-point-in-bounds check
Common Bugs and Solutions
Bug 1: Child Not Moving with Parent
Symptom: Dragging parent leaves children behind
Cause: parentNode property not set or position stored as absolute
Fix:
// Ensure parentNode is set
updateNode(childId, { parentNode: parentId })
// Convert to relative position
const parent = getNode(parentId)
const relativePos = {
x: child.computedPosition.x - parent.computedPosition.x,
y: child.computedPosition.y - parent.computedPosition.y
}
updateNode(childId, { position: relativePos })
Bug 2: Node Jumping When Set as Child
Symptom: Node teleports when assigned parent
Cause: Absolute position interpreted as relative after parent set
Fix: Convert position BEFORE setting parentNode:
const parent = getNode(parentId)
const child = getNode(childId)
// Step 1: Calculate relative position first
const relativePos = {
x: child.computedPosition.x - parent.computedPosition.x,
y: child.computedPosition.y - parent.computedPosition.y
}
// Step 2: Update both in same call
updateNode(childId, {
parentNode: parentId,
position: relativePos
})
Bug 3: Containment Detection Fails
Symptom: Tasks inside group not recognized
Cause: Comparing relative position against absolute bounds
Fix: Always use computedPosition for containment:
function isInsideGroup(task: Node, group: Node): boolean {
// WRONG: task.position (may be relative)
// RIGHT: task.computedPosition (always absolute)
const taskCenter = {
x: task.computedPosition.x + (task.dimensions?.width ?? 200) / 2,
y: task.computedPosition.y + (task.dimensions?.height ?? 100) / 2
}
return (
taskCenter.x >= group.computedPosition.x &&
taskCenter.x <= group.computedPosition.x + (group.dimensions?.width ?? 400) &&
taskCenter.y >= group.computedPosition.y &&
taskCenter.y <= group.computedPosition.y + (group.dimensions?.height ?? 300)
)
}
Bug 4: Position Persistence Issues
Symptom: Positions reset on page reload
Cause: Storing computedPosition instead of position for children
Fix: Store the correct coordinate type:
function saveNodePosition(node: Node) {
// For children: store relative position
// For root nodes: store absolute position
// node.position is ALWAYS what should be stored
await saveToDatabase({
id: node.id,
position: node.position, // NOT computedPosition
parentNode: node.parentNode
})
}
Bug 5: Stale Parent Reference
Symptom: "Stale parentGroupId" warnings, child outside parent bounds
Cause: Parent moved/deleted but child still references it
Fix: Validate and repair on load:
function validateHierarchy(nodes: Node[]) {
const nodeMap = new Map(nodes.map(n => [n.id, n]))
for (const node of nodes) {
if (node.parentNode && !nodeMap.has(node.parentNode)) {
console.warn(`Orphan node ${node.id} - parent ${node.parentNode} missing`)
// Convert to root node
node.parentNode = undefined
// Position is already absolute-equivalent, keep as-is
}
}
}
Resources
references/
coordinate-system.md- Deep dive into Vue Flow coordinate systemstroubleshooting-tree.md- Systematic problem diagnosis flowchartcode-patterns.md- Proven code patterns for common operationse2e-test-patterns.md- Playwright E2E test patterns for nested groups and parent movement
scripts/
vueFlowDebug.ts- TypeScript debugging utilities for Vue Flow
Verification Checklist
After applying fixes, verify:
- Dragging parent moves all children together
- Positions persist correctly across page reloads
- New tasks dropped in group are detected as children
- Nested groups maintain hierarchy when moved
- No position jumping on any operation
- Console shows no stale parent warnings