Canvas Debugging Skill
canvas-debugging
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.
SKILL.md
Full skill instructions
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- 시각적 레이아웃 설명
