| name | canvas-debugging |
| description | Debug canvas layout issues, grid positioning problems, and component overlap. Use when troubleshooting Konva canvas rendering, grid constraints, snap-to-grid behavior, or visual layout mismatches. Includes Canvas→CSS Grid conversion debugging. |
| allowed-tools | Read, Glob, Grep |
Canvas Debugging Skill
Visual Layout Builder의 Konva 기반 Canvas 시스템 디버깅을 위한 전문 스킬입니다. Grid 위치 계산, 컴포넌트 충돌, 스냅 동작 문제를 해결합니다.
When to Use
- Canvas에서 컴포넌트 위치가 잘못 표시될 때
- Grid 스냅이 예상대로 동작하지 않을 때
- 컴포넌트 겹침(overlap) 문제 발생 시
- Canvas→CSS Grid 변환 오류 디버깅
- Breakpoint 전환 시 레이아웃 문제
- 드래그 앤 드롭 동작 이상
components/canvas/관련 이슈
Canvas System Architecture
Core Components
components/canvas/
├── KonvaCanvas.tsx # Main canvas with Konva Stage/Layer
├── ComponentNode.tsx # Individual component rendering
├── ComponentPreview.tsx # Drag preview
├── Canvas.tsx # Canvas + component management
└── index.ts # Public exports
Canvas Coordinate System
Canvas Grid (0-based coordinates)
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│0,0│1,0│2,0│3,0│4,0│5,0│6,0│7,0│8,0│9,0│10 │11 │ Row 0
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│0,1│1,1│2,1│...│...│...│...│...│...│...│...│11,1 Row 1
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│...│...│...│...│...│...│...│...│...│...│...│...│ ...
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
CanvasLayout:
- x: Grid column start (0-based)
- y: Grid row start (0-based)
- width: Number of columns to span
- height: Number of rows to span
CSS Grid Conversion (1-based)
// Canvas (0-based) → CSS Grid (1-based)
gridColumn: `${x + 1} / ${x + width + 1}`
gridRow: `${y + 1} / ${y + height + 1}`
gridArea: `${y + 1} / ${x + 1} / ${y + height + 1} / ${x + width + 1}`
// Example: Canvas { x: 0, y: 0, width: 12, height: 1 }
// → CSS Grid: "1 / 1 / 2 / 13" (grid-area format)
Common Issues & Solutions
Issue 1: Component Position Mismatch
Symptom: Component appears in wrong position after drag
Debug Steps:
// 1. Check pixel-to-grid conversion
const cellWidth = canvasWidth / gridCols
const cellHeight = canvasHeight / gridRows
console.log('Cell dimensions:', { cellWidth, cellHeight })
// 2. Verify snap calculation
const gridX = Math.floor(pixelX / cellWidth)
const gridY = Math.floor(pixelY / cellHeight)
console.log('Grid position:', { gridX, gridY })
// 3. Check responsiveCanvasLayout
console.log('Component layout:', component.responsiveCanvasLayout?.[breakpoint])
Common Causes:
Math.round()vsMath.floor()inconsistency- Canvas padding/margin not accounted for
- Breakpoint mismatch (editing mobile, viewing desktop)
Issue 2: Component Overlap Detection
Symptom: Components overlap but no warning shown
Debug with schema-validation.ts:
import { validateSchema } from '@/lib/schema-validation'
const result = validateSchema(schema)
// Check for overlap warnings
const overlapWarnings = result.warnings?.filter(
w => w.code === 'CANVAS_COMPONENTS_OVERLAP'
)
console.log('Overlapping components:', overlapWarnings)
Overlap Detection Logic:
function checkOverlap(a: CanvasLayout, b: CanvasLayout): boolean {
// Check if rectangles overlap
return !(
a.x + a.width <= b.x || // a is left of b
b.x + b.width <= a.x || // b is left of a
a.y + a.height <= b.y || // a is above b
b.y + b.height <= a.y // b is above a
)
}
Issue 3: Grid Snap Not Working
Symptom: Components don't snap to grid when dragged
Debug Steps:
// lib/snap-to-grid.ts
export function snapToGrid(
x: number,
y: number,
cellWidth: number,
cellHeight: number
): { x: number; y: number } {
console.log('Input:', { x, y })
console.log('Cell size:', { cellWidth, cellHeight })
const snappedX = Math.round(x / cellWidth) * cellWidth
const snappedY = Math.round(y / cellHeight) * cellHeight
console.log('Snapped:', { snappedX, snappedY })
return { x: snappedX, y: snappedY }
}
Check Grid Constraints:
import { GRID_CONSTRAINTS } from '@/lib/schema-utils'
console.log('Grid constraints:', {
minCols: GRID_CONSTRAINTS.minCols, // 4
maxCols: GRID_CONSTRAINTS.maxCols, // 24
minRows: GRID_CONSTRAINTS.minRows, // 4
maxRows: GRID_CONSTRAINTS.maxRows // 50
})
Issue 4: Canvas Out of Bounds
Symptom: Component placed outside grid bounds
Validation Code: CANVAS_OUT_OF_BOUNDS
function validateBounds(layout: CanvasLayout, gridCols: number, gridRows: number) {
const issues = []
if (layout.x < 0 || layout.y < 0) {
issues.push('CANVAS_NEGATIVE_COORDINATE')
}
if (layout.x + layout.width > gridCols) {
issues.push(`Out of bounds: x(${layout.x}) + width(${layout.width}) > gridCols(${gridCols})`)
}
if (layout.y + layout.height > gridRows) {
issues.push(`Out of bounds: y(${layout.y}) + height(${layout.height}) > gridRows(${gridRows})`)
}
return issues
}
Issue 5: Breakpoint Layout Inheritance
Symptom: Component has layout in mobile but not in desktop
Debug:
// Check responsiveCanvasLayout for all breakpoints
const component = schema.components.find(c => c.id === 'c1')
console.log('Canvas layouts by breakpoint:')
for (const bp of schema.breakpoints) {
const layout = component?.responsiveCanvasLayout?.[bp.name]
console.log(` ${bp.name}:`, layout || 'MISSING')
}
// After normalization
const normalized = normalizeSchema(schema)
const normalizedComponent = normalized.components.find(c => c.id === 'c1')
console.log('After normalization:')
for (const bp of normalized.breakpoints) {
const layout = normalizedComponent?.responsiveCanvasLayout?.[bp.name]
console.log(` ${bp.name}:`, layout || 'STILL MISSING')
}
Issue 6: CSS Grid Conversion Errors
Symptom: AI-generated code doesn't match canvas layout
Debug canvas-to-grid.ts:
import { canvasToGridPositions } from '@/lib/canvas-to-grid'
const positions = canvasToGridPositions(
schema.components,
'desktop',
12, // gridCols
8 // gridRows
)
console.log('Grid positions:')
positions.forEach(p => {
console.log(` ${p.componentId}:`)
console.log(` gridColumn: ${p.gridColumn}`)
console.log(` gridRow: ${p.gridRow}`)
console.log(` gridArea: ${p.gridArea}`)
})
Expected Format:
c1 (Header):
gridColumn: "1 / 13" // Full width (12 cols)
gridRow: "1 / 2" // 1 row height
gridArea: "1 / 1 / 2 / 13"
Visual Layout Descriptor Debugging
import { describeVisualLayout } from '@/lib/visual-layout-descriptor'
const description = describeVisualLayout(
schema.components,
'desktop',
12,
8
)
console.log('Summary:', description.summary)
console.log('\nRow by row:')
description.rowByRow.forEach(row => console.log(' ', row))
console.log('\nSpatial relationships:')
description.spatialRelationships.forEach(rel => console.log(' ', rel))
console.log('\nImplementation hints:')
description.implementationHints.forEach(hint => console.log(' ', hint))
Canvas Validation Codes
| Code | Type | Description |
|---|---|---|
CANVAS_LAYOUT_ORDER_MISMATCH |
Warning | Canvas order ≠ DOM order |
COMPLEX_GRID_LAYOUT_DETECTED |
Warning | Side-by-side components |
CANVAS_COMPONENTS_OVERLAP |
Warning | Components overlapping |
CANVAS_OUT_OF_BOUNDS |
Warning | Outside grid bounds |
CANVAS_ZERO_SIZE |
Warning | width=0 or height=0 |
CANVAS_NEGATIVE_COORDINATE |
Error | x<0 or y<0 |
CANVAS_FRACTIONAL_COORDINATE |
Warning | Non-integer coordinates |
CANVAS_COMPONENT_NOT_IN_LAYOUT |
Warning | Not in layout.components |
MISSING_CANVAS_LAYOUT |
Warning | No canvas position |
Debugging Konva Rendering
Stage/Layer Issues
// In KonvaCanvas.tsx
useEffect(() => {
console.log('Stage dimensions:', {
width: stageRef.current?.width(),
height: stageRef.current?.height()
})
console.log('Layer children:', layerRef.current?.children?.length)
}, [])
Component Node Position
// In ComponentNode.tsx
const handleDragEnd = (e: Konva.KonvaEventObject<DragEvent>) => {
const node = e.target
console.log('Drag end position:', {
x: node.x(),
y: node.y(),
absolutePosition: node.absolutePosition()
})
// Convert to grid
const gridX = Math.floor(node.x() / cellWidth)
const gridY = Math.floor(node.y() / cellHeight)
console.log('Grid position:', { gridX, gridY })
}
Grid Constraint Validation
import { calculateMinimumGridSize } from '@/lib/grid-constraints'
// Check if grid can be reduced
const { minCols, minRows } = calculateMinimumGridSize(
schema.components,
'desktop'
)
console.log('Minimum required grid:', { minCols, minRows })
console.log('Current grid:', {
cols: breakpoint.gridCols,
rows: breakpoint.gridRows
})
if (breakpoint.gridCols < minCols || breakpoint.gridRows < minRows) {
console.warn('Grid too small for components!')
}
Quick Debug Checklist
# 1. Check current breakpoint
console.log('Current breakpoint:', currentBreakpoint)
# 2. Verify grid configuration
console.log('Grid config:', breakpoint)
# 3. Log all component positions
schema.components.forEach(c => {
const layout = c.responsiveCanvasLayout?.[currentBreakpoint]
console.log(`${c.id} (${c.name}):`, layout || 'NO LAYOUT')
})
# 4. Run validation
const result = validateSchema(schema)
console.log('Validation:', result)
# 5. Check CSS Grid output
const gridPositions = canvasToGridPositions(
schema.components,
currentBreakpoint,
breakpoint.gridCols,
breakpoint.gridRows
)
console.log('CSS Grid positions:', gridPositions)
Reference Files
components/canvas/KonvaCanvas.tsx- Canvas 렌더링components/canvas/ComponentNode.tsx- 컴포넌트 노드lib/canvas-to-grid.ts- Canvas→CSS Grid 변환lib/canvas-utils.ts- Canvas 유틸리티lib/grid-constraints.ts- Grid 제약 조건lib/snap-to-grid.ts- 스냅 로직lib/schema-validation.ts- Canvas 검증 로직lib/visual-layout-descriptor.ts- 시각적 레이아웃 설명