# 2026-02-06 작업 기록 ## 요약 v5.1 자동 줄바꿈 + 검토 필요 시스템 완성, 브레이크포인트 재설계, 세로 자동 확장 구현 --- ## 완료 ### 브레이크포인트 재설계 - [x] GRID_BREAKPOINTS 값 수정 (기기 기반) - [x] detectGridMode() 조건 수정 - [x] useDeviceOrientation.ts TABLET_MIN 768로 변경 - [x] 뷰어에서 detectGridMode() 사용하여 일관성 확보 ### 세로 자동 확장 - [x] VIEWPORT_PRESETS에서 height 속성 제거 - [x] dynamicCanvasHeight useMemo 추가 - [x] MIN_CANVAS_HEIGHT, CANVAS_EXTRA_ROWS 상수 추가 - [x] gridLabels 동적 계산 (행 수 자동 조정) - [x] gridCells 동적 계산 (PopRenderer) - [x] 뷰어 프리뷰 모드 스크롤 지원 ### 자동 줄바꿈 시스템 (v5.1) - [x] convertAndResolvePositions() 자동 줄바꿈 로직 - [x] 원본 col 보존 로직 - [x] 초과 컴포넌트 맨 아래 배치 - [x] colSpan 자동 축소 ### 검토 필요 시스템 - [x] needsReview() 함수 추가 - [x] OutOfBoundsPanel → ReviewPanel 변경 - [x] 파란색 테마 (안내 느낌) - [x] 클릭 시 컴포넌트 선택 ### 버그 수정 - [x] hiddenComponentIds 중복 정의 에러 수정 - [x] useDrop 의존성 배열 수정 - [x] 검토 필요 패널 모드별 표시 불일치 수정 ### 그리드 셀 크기 강제 고정 (v5.2.1) - [x] gridAutoRows → gridTemplateRows 변경 (행 높이 강제 고정) - [x] dynamicRowCount를 gridStyle과 gridCells에서 공유 - [x] 컴포넌트 overflow: visible → overflow: hidden 변경 - [x] PopRenderer dynamicRowCount에서 숨김 컴포넌트 제외 - [x] PopCanvas와 PopRenderer의 여유행 기준 통일 (+3) - [x] 디버깅용 console.log 2개 삭제 - [x] 뷰어 page.tsx viewportWidth 선언 순서 수정 --- ## 브레이크포인트 변경 상세 ### 변경 전 → 변경 후 | 모드 | 변경 전 | 변경 후 | 근거 | |------|--------|--------|------| | mobile_portrait | ~599px | ~479px | 스마트폰 세로 최대 440px | | mobile_landscape | 600~839px | 480~767px | 767px까지 스마트폰 | | tablet_portrait | 840~1023px | 768~1023px | iPad Mini 768px 포함 | | tablet_landscape | 1024px+ | 동일 | 변경 없음 | ### 연구 결과 (기기별 CSS 뷰포트) | 기기 | CSS 뷰포트 너비 | |------|----------------| | iPhone SE | 375px | | iPhone 16 Pro | 402px | | Galaxy S25 Ultra | 440px | | iPad Mini 7 | 768px | | iPad Pro 11 | 834px (세로), 1194px (가로) | | iPad Pro 13 | 1024px (세로), 1366px (가로) | --- ## 세로 자동 확장 상세 ### 핵심 상수 ```typescript const MIN_CANVAS_HEIGHT = 600; // 최소 캔버스 높이 (px) const CANVAS_EXTRA_ROWS = 3; // 항상 유지되는 여유 행 수 ``` ### 동적 높이 계산 로직 ```typescript const dynamicCanvasHeight = useMemo(() => { const visibleComps = Object.values(layout.components) .filter(comp => !hiddenComponentIds.includes(comp.id)); if (visibleComps.length === 0) return MIN_CANVAS_HEIGHT; const maxRowEnd = visibleComps.reduce((max, comp) => { const pos = getEffectivePosition(comp); return Math.max(max, pos.row + pos.rowSpan); }, 1); const totalRows = maxRowEnd + CANVAS_EXTRA_ROWS; const height = totalRows * (rowHeight + gap) + padding * 2; return Math.max(MIN_CANVAS_HEIGHT, height); }, [dependencies]); ``` ### 영향받는 영역 | 영역 | 변경 | |------|------| | 캔버스 컨테이너 | minHeight: dynamicCanvasHeight | | 디바이스 스크린 | minHeight: dynamicCanvasHeight | | 행 라벨 | 동적 행 수 계산 | | 격자 셀 | 동적 행 수 계산 | --- ## 자동 줄바꿈 로직 상세 ### 처리 단계 ``` 1. 비율 변환 + 원본 col 보존 converted = map(comp => ({ position: convertPositionToMode(comp.position), originalCol: comp.position.col, // 원본 보존 })) 2. 정상 vs 초과 분리 normalComponents = filter(originalCol <= targetColumns) overflowComponents = filter(originalCol > targetColumns) 3. 초과 컴포넌트 맨 아래 배치 maxRow = normalComponents의 최대 (row + rowSpan - 1) overflowComponents → col=1, row=maxRow+1 4. colSpan 자동 축소 if (colSpan > targetColumns) colSpan = targetColumns 5. 겹침 해결 resolveOverlaps([...normalComponents, ...wrappedComponents]) ``` --- ## 대화 핵심 ### 반응형 불일치 문제 **사용자 리포트**: > "아이폰 SE, iPad Pro 프리셋은 잘 되는데, > 브라우저 수동 리사이즈 시 6칸 모드가 적용 안 되는 것 같아" **원인 분석**: - useResponsiveMode: width/height 비율로 landscape/portrait 판정 - GRID_BREAKPOINTS: 순수 너비 기반 - 768~839px 구간에서 불일치 발생 **해결**: - 뷰어에서 detectGridMode(viewportWidth) 사용 - 프리뷰 모드만 useResponsiveModeWithOverride 유지 ### 세로 무한 스크롤 결정 **사용자 질문**: > "우리 화면 모드는 너비만 신경쓰면 되잖아? > 세로는 무한 스크롤이 가능해야 하겠네?" **확인 사항**: 1. 너비만 신경쓰면 됨 ✅ 2. 캔버스 세로 무한 스크롤 필요 ✅ 3. 뷰어에서 터치 스크롤 지원 ✅ **구현 방식 선택**: - 수동 행 추가 방식 vs **자동 확장 방식 (채택)** - 이유: 여유 공간 3행 자동 유지, 사용자 부담 최소화 --- ## 빌드 결과 ``` exit_code: 0 주요 변경 파일: 6개 ``` --- ## 관련 링크 - ADR: [decisions/005-breakpoint-redesign.md](../decisions/005-breakpoint-redesign.md) - ADR: [decisions/006-auto-wrap-review-system.md](../decisions/006-auto-wrap-review-system.md) - 이전 세션: [sessions/2026-02-05.md](./2026-02-05.md) --- ## 이번 작업에서 배운 것 ### 새로 알게 된 기술 개념 - **gridAutoRows vs gridTemplateRows**: `gridAutoRows`는 행의 *최소* 높이만 보장하고 콘텐츠에 따라 늘어날 수 있음. `gridTemplateRows`는 행 높이를 *강제 고정*함. 가이드 셀과 컴포넌트가 같은 Grid 컨테이너에 있을 때, 컴포넌트 콘텐츠가 행 높이를 밀어내면 인접한 빈 가이드 셀 크기도 함께 변해 시각적 불일치가 발생함. ### 발생했던 에러와 원인 패턴 | 에러 | 원인 패턴 | |------|-----------| | 그리드 셀 크기 불균일 | 같은 CSS Grid에서 gridAutoRows(최소값)를 사용하면 콘텐츠가 행 높이를 변형시킴 | | Canvas vs Renderer 행 수 불일치 | 같은 데이터(행 수)를 두 곳에서 계산하면서 필터 조건(숨김 제외)이 달랐음 | | 디버깅 console.log 잔존 | 기능 완료 후 정리 단계를 생략함 | | viewportWidth 참조 순서 | 변수 사용 코드가 선언 코드보다 위에 위치 (JS 호이스팅으로 동작은 하지만 가독성 저하) | ### 다음에 비슷한 작업할 때 주의할 점 1. **CSS Grid에서 "고정 크기" 셀이 필요하면 `gridTemplateRows`를 사용**하고, `gridAutoRows`는 동적 추가행 대비용으로만 유지 2. **같은 데이터를 여러 곳에서 계산할 때, 필터 조건이 동일한지 반드시 비교** (숨김 제외 등) 3. **기능 완료 후 `console.log`를 Grep으로 검색하여 디버깅 로그 정리** 4. **변수 선언 순서는 의존 관계 순서와 일치**시켜야 가독성과 유지보수성 확보 --- ## 중단점 > **다음 작업**: Phase 4 실제 컴포넌트 구현 > - pop-label, pop-button 등 실제 렌더링 구현 > - 데이터 바인딩 연결 > - STATUS.md의 "다음 작업" 섹션 참조 --- ## 다음 작업자 참고 1. **테스트 필요** - 아이폰 SE 실기기 테스트 - iPad Mini 세로 모드 확인 - 브라우저 리사이즈로 모드 전환 확인 2. **향후 작업** - Phase 4: 실제 컴포넌트 구현 (pop-label, pop-button 등) - 데이터 바인딩 연결 - 워크플로우 연동