# POP 변경 이력 형식: [Keep a Changelog](https://keepachangelog.com/) --- ## [미출시] - Phase 4: 실제 컴포넌트 구현 (pop-field, pop-button 등) - 데이터 바인딩 구현 - 워크플로우 연동 --- ## [2026-02-09] origin/main 병합 (ksh-v2-work-merge-test) ### 배경 (왜 이 작업이 필요했는가) **상황**: - `ksh-v2-work` 브랜치에서 POP 디자이너 개발이 진행되는 동안, `main` 브랜치에서 데스크톱 ScreenDesigner 관련 대규모 업데이트(`feature/v2-unified-renewal` PR #386)가 병합됨 - 두 브랜치가 `ScreenDesigner.tsx`를 동시에 수정하여 병합 충돌 발생 - 안전한 병합을 위해 `ksh-v2-work-merge-test` 테스트 브랜치에서 작업 **병합 통계**: - 소스: `origin/main` (86 커밋) - 대상: `ksh-v2-work-merge-test` (ksh-v2-work에서 분기, 14 커밋) - 분기점: `3fca677f` (feat: V2Media 컴포넌트 추가) - 변경 파일: 207개 (43,535줄 추가, 3,547줄 삭제) - 충돌 파일: 1개 (`ScreenDesigner.tsx`) ### Merged (origin/main에서 가져온 주요 변경) - **ScreenDesigner 데스크톱 기능 강화** - 그룹 정렬/분배/크기 맞춤 (handleGroupAlign, handleGroupDistribute, handleMatchSize) - 라벨 토글 (handleToggleAllLabels) - 단축키 도움말 모달 (showShortcutsModal) - 디버그 console.log 정리 - **백엔드 신규 기능** - 스케줄 관리 API (scheduleController, scheduleRoutes, scheduleService) - 파일 관리 개선 (fileController, fileRoutes) - 테이블 관리 확장 (tableManagementController) - 넘버링 규칙 개선 (numberingRuleController) - **프론트엔드 신규/수정** - 레이어 매니저 패널, 레이어 조건 패널 - 화면 복사 모달, 편집 모달 - InteractiveDataTable, InteractiveScreenViewer - 넘버링 규칙 디자이너 - 화면 그룹 트리뷰, 화면 관계 플로우 ### 충돌 해결 (ScreenDesigner.tsx) 3건의 충돌을 수동 해결: | 충돌 | 영역 | 해결 방식 | |------|------|----------| | 1. 함수 시그니처 | `isPop`, `defaultDevicePreview` props 추가 | ksh-v2-work 유지 (POP 모드 지원), 중복 `usePanelState` 제거 | | 2. 저장 로직 | POP/V2/Legacy 3단계 분기 | ksh-v2-work 유지 (3단계 분기), console.log 제거 | | 3. 툴바 props | 정렬/분배/크기맞춤/라벨토글/단축키 | origin/main 채택 (데스크톱 신규 기능 모두 포함) | ### 검증 결과 | 항목 | 결과 | |------|------| | 충돌 마커 잔존 | 없음 | | TypeScript 컴파일 | 신규 에러 없음 (기존 에러만) | | 프론트엔드 빌드 | 성공 | | 백엔드 빌드 | 신규 에러 없음 (`docx`/`bwip-js` 기존 이슈만) | | 시맨틱 충돌 | 없음 | | 린트 | 기능 에러 없음 (들여쓰기 차이만) | ### 주의사항 - **Conflict 3 영역 들여쓰기**: origin/main에서 가져온 툴바 JSX(L5745~6631)의 들여쓰기가 ksh-v2-work와 2칸 차이. 기능에는 영향 없으나, 추후 포매팅 정리 권장 - **기존 타입 에러**: `GridSettings`/`GridUtilSettings` 불일치, `SCREEN_RESOLUTIONS` export type 문제 등은 병합 이전부터 존재하던 기술 부채 ### 수정 파일 | 파일 | 변경 내용 | |------|----------| | `ScreenDesigner.tsx` | 3건 충돌 수동 해결 (함수 시그니처, 저장 로직, 툴바 props) | | 외 207개 파일 | origin/main에서 자동 병합 | --- ## [2026-02-06] v5.2.1 그리드 셀 크기 강제 고정 ### 배경 (왜 이 작업이 필요했는가) **문제 상황**: - 4칸 모드에서 특정 행의 가이드 셀이 다른 행보다 작게 표시됨 - `gridAutoRows`는 최소 높이만 보장하여, 컴포넌트 콘텐츠가 행 높이를 밀어내면 인접 빈 셀도 영향받음 - "셀의 크기 = 컴포넌트의 크기"라는 핵심 설계 원칙이 시각적으로 깨짐 ### Changed - **gridAutoRows → gridTemplateRows** (PopRenderer.tsx) ```typescript // 변경 전: 최소 높이만 보장 (콘텐츠에 따라 늘어남) gridAutoRows: `${breakpoint.rowHeight}px` // 변경 후: 행 높이 강제 고정 gridTemplateRows: `repeat(${dynamicRowCount}, ${breakpoint.rowHeight}px)` gridAutoRows: `${breakpoint.rowHeight}px` // 동적 추가행 대비 유지 ``` - **dynamicRowCount 분리** (PopRenderer.tsx) - gridCells 내부 → 독립 useMemo로 분리 - gridStyle과 gridCells에서 공유 - **컴포넌트 overflow 변경** (PopRenderer.tsx) - `overflow-visible` → `overflow-hidden` - 컴포넌트 콘텐츠가 셀 경계를 벗어나지 않도록 강제 ### Fixed - **PopRenderer dynamicRowCount에서 숨김 컴포넌트 포함 문제** - PopCanvas는 숨김 제외하여 높이 계산, PopRenderer는 포함하여 계산 → 기준 불일치 - PopRenderer에도 숨김 필터 추가, 여유행 +5 → +3으로 통일 - **디버깅 console.log 잔존** (PopCanvas.tsx) - reviewComponents useMemo 내 console.log 2개 삭제 - **뷰어 viewportWidth 선언 순서** (page.tsx) - currentModeKey보다 뒤에 선언되어 있던 viewportWidth를 앞으로 이동 ### 수정 파일 | 파일 | 변경 내용 | |------|----------| | `PopRenderer.tsx` | gridTemplateRows 강제 고정, dynamicRowCount 분리, overflow-hidden, 숨김 필터 추가 | | `PopCanvas.tsx` | 디버깅 console.log 삭제 | | `page.tsx (뷰어)` | viewportWidth 선언 순서 수정 | --- ## [2026-02-06] v5.2 브레이크포인트 재설계 + 세로 자동 확장 ### 배경 (왜 이 작업이 필요했는가) **문제 상황**: - 뷰어에서 브라우저 수동 리사이즈 시 768~839px 구간에서 모드 불일치 - useResponsiveMode 훅과 GRID_BREAKPOINTS 상수 간 기준 불일치 - 기존 브레이크포인트가 실제 기기 뷰포트와 맞지 않음 **사용자 요구사항**: - "현장 모바일 기기 8~14인치, 핸드폰은 아이폰 미니 ~ 갤럭시 울트라" - "세로는 신경쓸 필요 없고 무한 스크롤 가능해야 함" ### Changed - **브레이크포인트 재설계** (pop-layout.ts) | 모드 | 변경 전 | 변경 후 | 근거 | |------|--------|--------|------| | mobile_portrait | ~599px | ~479px | 스마트폰 세로 최대 440px | | mobile_landscape | 600~839px | 480~767px | 스마트폰 가로 | | tablet_portrait | 840~1023px | 768~1023px | iPad Mini 768px 포함 | | tablet_landscape | 1024px+ | 동일 | - | - **detectGridMode() 조건 수정** (pop-layout.ts) ```typescript if (viewportWidth < 480) return "mobile_portrait"; // was 600 if (viewportWidth < 768) return "mobile_landscape"; // was 840 if (viewportWidth < 1024) return "tablet_portrait"; ``` - **BREAKPOINTS.TABLET_MIN 변경** (useDeviceOrientation.ts) - 768 (was 840) - **VIEWPORT_PRESETS에서 height 제거** (PopCanvas.tsx) - width만 유지, 세로는 무한 스크롤 ### Added - **세로 자동 확장** (PopCanvas.tsx) - `MIN_CANVAS_HEIGHT = 600`: 최소 캔버스 높이 - `CANVAS_EXTRA_ROWS = 3`: 항상 유지되는 여유 행 수 - `dynamicCanvasHeight`: 컴포넌트 배치 기반 동적 계산 - **격자 셀 동적 계산** (PopRenderer.tsx) - 고정 20행 → maxRowEnd + 5 동적 계산 - **뷰어 일관성 확보** (page.tsx) - 프리뷰 모드: useResponsiveModeWithOverride 유지 - 일반 모드: detectGridMode(viewportWidth) 직접 사용 ### Fixed - **뷰어 반응형 모드 불일치** - 768~839px 구간에서 6칸/8칸 모드 불일치 해결 - **hiddenComponentIds 중복 정의 에러** - 라인 410-412 중복 useMemo 제거 ### Technical Details ``` 브레이크포인트 재설계 근거 (실제 기기 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 (가로) | → 768px, 1024px가 업계 표준 (Tailwind, Bootstrap 동일) ``` ``` 세로 자동 확장 로직: const dynamicCanvasHeight = useMemo(() => { const maxRowEnd = visibleComps.reduce((max, comp) => { return Math.max(max, comp.row + comp.rowSpan); }, 1); const totalRows = maxRowEnd + CANVAS_EXTRA_ROWS; // +3행 여유 const height = totalRows * (rowHeight + gap) + padding * 2; return Math.max(MIN_CANVAS_HEIGHT, height); // 최소 600px }, [...]); ``` ### 수정 파일 | 파일 | 변경 내용 | |------|----------| | `pop-layout.ts` | GRID_BREAKPOINTS 값 수정, detectGridMode() 조건 수정 | | `useDeviceOrientation.ts` | BREAKPOINTS.TABLET_MIN = 768 | | `PopCanvas.tsx` | VIEWPORT_PRESETS height 제거, dynamicCanvasHeight 추가 | | `PopRenderer.tsx` | gridCells 동적 행 수 계산 | | `page.tsx (뷰어)` | detectGridMode() 사용 | --- ## [2026-02-06] v5.1 자동 줄바꿈 + 검토 필요 시스템 ### 배경 (왜 이 작업이 필요했는가) **문제 상황**: - 12칸에서 배치한 컴포넌트가 4칸 모드로 전환하면 "화면 밖" 패널로 이동하여 뷰어에서 안 보임 - 사용자가 모든 모드를 수동으로 편집해야 하는 부담 - "화면 밖" 개념이 실제로는 "검토 필요" 알림 역할이었음 **해결 방향**: - 자동 줄바꿈: col > maxCol인 컴포넌트를 자동으로 맨 아래에 배치 - 정보 손실 방지: 모든 컴포넌트가 항상 그리드 안에 표시됨 - 검토 필요 알림: 오버라이드 없으면 "검토 필요" 표시 (자동 배치 상태) ### Added - **자동 줄바꿈 로직** (gridUtils.ts) - `convertAndResolvePositions()` 수정 - 원본 col 보존 로직 추가 - 정상 컴포넌트 vs 초과 컴포넌트 분리 - 초과 컴포넌트를 맨 아래에 순차 배치 (col=1, row=맨아래+1) - colSpan 자동 축소 (targetColumns 초과 방지) - **검토 필요 판별 함수** (gridUtils.ts) - `needsReview()` 신규 함수 - 기준: 12칸 아니고 + 오버라이드 없으면 → 검토 필요 - 간단한 로직: "이 모드에서 편집했냐 안 했냐" - **검토 필요 패널** (PopCanvas.tsx) - `ReviewPanel`: "화면 밖" → "검토 필요"로 이름 변경 - `ReviewItem`: 클릭 시 해당 컴포넌트 선택 (드래그 없음) - 자동 배치 뱃지 표시 - 파란색 테마 (경고 아닌 안내 느낌) ### Changed - **isOutOfBounds() Deprecated** (gridUtils.ts) - `@deprecated` 주석 추가 - needsReview()로 대체 권장 - 하위 호환을 위해 함수는 유지 - **"화면 밖" 패널 역할 변경** (PopCanvas.tsx) - 기존: col > maxCol → 화면 밖 (드래그로 복원) - 변경: 오버라이드 없음 → 검토 필요 (클릭으로 선택) - 숨김 기능과 완전히 분리 (별도 유지) ### Fixed - **정보 손실 문제 해결** - 모든 컴포넌트가 항상 그리드 안에 배치됨 - 뷰어에서도 자동 배치가 적용되어 모두 표시됨 ### Technical Details ``` 자동 줄바꿈 로직: 1. convertAndResolvePositions() 호출 components: [ {id: "A", position: {col:1, ...}}, {id: "B", position: {col:5, ...}} ] targetMode: "mobile_portrait" (4칸) 2. 비율 변환 + 원본 col 보존 converted: [ {id: "A", position: {col:1, ...}, originalCol: 1}, {id: "B", position: {col:2, ...}, originalCol: 5} // col은 변환됨, 원본은 5 ] 3. 정상 vs 초과 분리 normalComponents: [A] // originalCol ≤ 4 overflowComponents: [B] // originalCol > 4 4. 맨 아래 배치 maxRow = A의 (row + rowSpan - 1) = 1 B: col=1, row=2 (맨 아래에 자동 배치) 5. 겹침 해결 resolveOverlaps([A, B], 4) // 최종 위치 확정 6. 검토 필요 판별 needsReview("mobile_portrait", false) // 오버라이드 없음 → true → ReviewPanel에 B 표시 ``` ``` 검토 필요 vs 숨김: 구분 | 검토 필요 | 숨김 ------------- | ---------------------- | ------------------- 역할 | 자동 배치 알림 | 의도적 숨김 뷰어에서 | 보임 (자동 배치) | 안 보임 디자이너에서 | ReviewPanel 표시 | HiddenPanel 표시 판단 기준 | 오버라이드 없음 | hidden 배열에 ID 색상 테마 | 파란색 (안내) | 회색 (제외) ``` ### 수정 파일 | 파일 | 변경 내용 | |------|----------| | `gridUtils.ts` | convertAndResolvePositions 자동 줄바꿈, needsReview 추가, isOutOfBounds deprecated | | `PopCanvas.tsx` | OutOfBoundsPanel → ReviewPanel 변경, needsReview 필터링 | | `PopRenderer.tsx` | isOutOfBounds import 제거 (사용 안 함) | | `README.md` | v5.1 버전 표시, 최신 기능 요약 | | `CHANGELOG.md` | v5.1 항목 추가 | --- ## [2026-02-05 심야] 반응형 레이아웃 + 숨김 기능 완성 ### 배경 (왜 이 작업이 필요했는가) **문제 상황**: - 12칸 모드에서 배치한 컴포넌트가 4칸 모드에서 초과됨 - 모드별로 컴포넌트 위치/크기를 다르게 설정할 방법 없음 - 특정 모드에서만 컴포넌트를 숨길 방법 없음 **해결 방향**: - 모드별 오버라이드 시스템으로 위치/크기 개별 저장 - 화면 밖 컴포넌트를 별도 패널에 표시하고 드래그로 재배치 - 숨김 기능으로 특정 모드에서 컴포넌트 제외 ### Added - **모드별 오버라이드 시스템** (PopDesigner.tsx, pop-layout.ts) - `PopModeOverrideV5.positions`: 모드별 컴포넌트 위치 저장 - `PopModeOverrideV5.hidden`: 모드별 숨김 컴포넌트 ID 배열 - `getEffectiveComponentPosition()`: 오버라이드된 위치 반환 - 드래그/리사이즈 시 자동으로 오버라이드 저장 - **화면 밖 컴포넌트 패널** (PopCanvas.tsx) - `OutOfBoundsPanel`: 현재 모드에서 초과하는 컴포넌트 표시 - `OutOfBoundsItem`: 드래그 가능한 회색 컴포넌트 카드 - `isOutOfBounds()`: 컴포넌트가 현재 모드 칸 수 초과 여부 판단 - 클릭하면 숨김 패널로 이동 - **숨김 기능** (PopDesigner.tsx, PopCanvas.tsx) - `HiddenPanel`: 숨김 처리된 컴포넌트 표시 - `HiddenItem`: 드래그로 숨김 해제 가능 - `handleHideComponent()`: 컴포넌트 숨김 처리 - `handleUnhideComponent()`: 숨김 해제 (handleMoveComponent에 통합) - 숨김 방법 3가지: 1. 그리드 → 숨김패널 드래그 2. H키 단축키 3. 화면밖 컴포넌트 클릭 - **리사이즈 겹침 검사** (PopRenderer.tsx) - `checkResizeOverlap()`: 리사이즈 시 다른 컴포넌트와 겹침 검사 - 겹치면 리사이즈 취소 및 toast 알림 - **원본으로 되돌리기** (PopDesigner.tsx) - `handleResetToDefault()`: 현재 모드 오버라이드 삭제 - 자동 위치 계산으로 복원 ### Fixed - **숨김 컴포넌트 드래그 안됨 버그** - 원인: `onUnhideComponent`와 `onMoveComponent`가 별도로 호출되어 상태 충돌 - 해결: `handleMoveComponent`에서 숨김 해제 로직 통합 (단일 상태 업데이트) - **그리드 범위 초과 에러** - 원인: 드롭 위치 + colSpan이 칸 수 초과 - 해결: 드롭 시 `adjustedCol` 계산하여 자동으로 왼쪽으로 밀어서 배치 - **getAllEffectivePositions에 숨김 컴포넌트 포함** - 해결: 숨김 및 화면밖 컴포넌트를 결과에서 제외 - **Expected drag drop context 에러 (뷰어 페이지)** - 원인: `DraggableComponent`에서 `useDrag` 훅이 `DndProvider` 없이 호출됨 - 해결: `isDesignMode=false`일 때 `DraggableComponent` 대신 일반 `div`로 렌더링 ### Changed - **PopModeOverrideV5 타입 확장** ```typescript interface PopModeOverrideV5 { positions?: Record>; // 위치 오버라이드 hidden?: string[]; // 숨김 컴포넌트 ID 배열 } ``` - **12칸 모드(tablet_landscape) 제한** - 기본 모드이므로 숨김 기능 비활성화 - 화면밖 패널 표시 안함 - 위치 변경은 기본 position에 직접 저장 - **패널 레이아웃 재구성** (PopCanvas.tsx) - 오른쪽에 화면밖 패널 + 숨김 패널 세로 배치 - 12칸 모드에서는 패널 숨김 ### Technical Details ``` 오버라이드 데이터 흐름: 1. 컴포넌트 드래그/리사이즈 ↓ 2. currentMode 확인 ↓ 3-a. tablet_landscape → layout.components[id].position 직접 수정 3-b. 다른 모드 → layout.overrides[mode].positions[id]에 저장 ↓ 4. getEffectiveComponentPosition()이 우선순위대로 반환 우선순위: overrides > autoResolved > 기본 position 숨김 기능 흐름: 1. 숨김 요청 (드래그/H키/클릭) ↓ 2. layout.overrides[mode].hidden 배열에 ID 추가 ↓ 3. PopRenderer에서 hidden 체크 → 렌더링 제외 ↓ 4. HiddenPanel에서 표시 ↓ 5. 드래그로 그리드에 복원 → hidden 배열에서 제거 + 위치 업데이트 (단일 상태 업데이트) ``` ### 수정 파일 | 파일 | 변경 내용 | |------|----------| | `pop-layout.ts` | PopModeOverrideV5.hidden 추가 | | `PopDesigner.tsx` | handleHideComponent, handleUnhideComponent 통합, 오버라이드 저장 | | `PopCanvas.tsx` | OutOfBoundsPanel, HiddenPanel 추가, 드롭 위치 자동 조정 | | `PopRenderer.tsx` | 숨김 필터링, 리사이즈 겹침 검사 | | `gridUtils.ts` | getAllEffectivePositions에서 숨김/화면밖 제외, isOutOfBounds 함수 | --- ## [2026-02-05 저녁] 드래그앤드롭 완전 수정 ### 배경 (왜 좌표 계산이 틀렸는가) **문제 상황**: - 컴포넌트를 아래로 드래그해도 위로 올라감 - Row 92 같은 비정상적인 좌표로 배치됨 - 드래그 이동/리사이즈가 전혀 작동하지 않음 **핵심 원인**: 캔버스에 `transform: scale(0.8)` 적용 시 좌표 계산 불일치 ``` 문제: - getBoundingClientRect() → 스케일 적용된 크기 반환 (예: 1024px → 819px) - getClientOffset() → 뷰포트 기준 실제 마우스 좌표 - 이 둘을 그대로 계산하면 좌표가 완전히 틀림 ``` **해결**: 단순한 상대 좌표 + 스케일 보정 ```typescript // 캔버스 내 상대 좌표 (스케일 보정) const relX = (마우스X - 캔버스left) / canvasScale; const relY = (마우스Y - 캔버스top) / canvasScale; calcGridPosition(relX, relY, customWidth, ...); // 실제 캔버스 크기 사용 ``` ### Added - **`calcGridPosition()` 함수** (PopCanvas.tsx) - 캔버스 내 상대 좌표를 그리드 좌표로 변환 - 패딩, gap, 셀 너비를 고려한 정확한 계산 - **공통 DND 상수** (constants/dnd.ts) - `DND_ITEM_TYPES.COMPONENT`: 팔레트에서 새 컴포넌트 - `DND_ITEM_TYPES.MOVE_COMPONENT`: 기존 컴포넌트 이동 - 3개 파일에서 중복 정의되던 것을 통합 ### Fixed - **스케일 보정 누락** - 캔버스 줌(scale)이 적용된 상태에서 좌표 계산 오류 - `(offset - rect.left) / scale`로 보정 - **DND 타입 상수 불일치** - PopCanvas: `"component"`, `"MOVE_COMPONENT"` - PopRenderer: `"MOVE_COMPONENT"` (하드코딩) - ComponentPalette: `"component"` (로컬 정의) - 모두 공통 상수로 통합 - **컴포넌트 중첩(겹침) 문제** - 원인: `toast` import 누락으로 겹침 감지 로직이 실행 안됨 - 해결: `sonner`에서 toast import 추가 - 겹침 시 `findNextEmptyPosition()`으로 자동 재배치 - **리사이즈 핸들 작동 안됨** - 원인: `useDrop` 훅 2개가 같은 `canvasRef`에 중복 적용 - 해결: 단일 `useDrop`으로 통합 (`COMPONENT` + `MOVE_COMPONENT` 모두 처리) - **불필요한 toast 메시지 제거** - "컴포넌트가 이동되었습니다" 알림 삭제 ### Changed - **mouseToGridPosition 단순화** - 복잡한 DOMRect 전달 대신 필요한 값만 직접 전달 - gridUtils.ts의 함수는 유지 (다른 곳에서 사용) ### Technical Details ``` 좌표 변환 흐름 (수정 후): 1. 마우스 드롭 offset = monitor.getClientOffset() // 뷰포트 기준 {x: 500, y: 300} 2. 캔버스 위치 canvasRect = canvasRef.getBoundingClientRect() // {left: 250, top: 100} 3. 스케일 보정된 상대 좌표 relX = (500 - 250) / 0.8 = 312.5 // 캔버스 내 실제 X relY = (300 - 100) / 0.8 = 250 // 캔버스 내 실제 Y 4. 그리드 좌표 계산 calcGridPosition(312.5, 250, 1024, 12, 48, 16, 24) → { col: 5, row: 4 } ``` ### 수정 파일 | 파일 | 변경 내용 | |------|----------| | `PopCanvas.tsx` | calcGridPosition 추가, 스케일 보정 적용 | | `PopDesigner.tsx` | toast 메시지 제거 | | `PopRenderer.tsx` | DND 상수 import | | `ComponentPalette.tsx` | DND 상수 import | | `constants/dnd.ts` | 새 파일 (DND 타입 상수) | | `constants/index.ts` | 새 파일 (export) | --- ## [2026-02-05 오후] 그리드 가이드 CSS Grid 통합 ### 배경 (왜 재설계했는가) **문제 상황**: - GridGuide.tsx(SVG 기반)와 PopRenderer.tsx(CSS Grid)가 좌표계 불일치 - 격자선과 컴포넌트가 정렬되지 않음 ("무늬가 따로 논다") - 행/열 라벨이 4부터 시작하는 등 오류 **핵심 원칙**: > "격자선은 컴포넌트와 같은 좌표계에서 태어나야 한다" **결정**: SVG 격자 삭제, CSS Grid 기반 통합 → 상세: [decisions/004-grid-guide-integration.md](./decisions/004-grid-guide-integration.md) ### Breaking Changes - `GridGuide.tsx` 삭제 (SVG 기반 격자) ### Added - **CSS Grid 기반 격자 셀** (PopRenderer.tsx) - `gridCells`: 12x20 = 240개 실제 DOM 셀 - `border-dashed border-blue-300/40` 스타일 - 컴포넌트는 `z-index:10`으로 위에 표시 - `showGridGuide` prop으로 ON/OFF - **행/열 라벨** (PopCanvas.tsx) - 열 라벨: 1~12 (캔버스 상단) - 행 라벨: 1~20 (캔버스 좌측) - absolute positioning으로 정확한 정렬 - 줌/패닝에 연동 - **그리드 토글 버튼** (PopCanvas.tsx) - "그리드 ON/OFF" 버튼 추가 - 격자 표시 상태 관리 ### Changed - **컴포넌트 타입 단순화** - `PopComponentType`: `pop-sample` 1개로 단순화 - `DEFAULT_COMPONENT_GRID_SIZE`: `pop-sample` 전용 - `ComponentPalette.tsx`: 샘플 박스 1개만 표시 - `PopRenderer.tsx`: 샘플 박스 렌더링으로 단순화 ### Technical Details ``` 역할 분담: - PopRenderer: 격자 셀(div) + 컴포넌트 (같은 CSS Grid 좌표계) - PopCanvas: 라벨 + 줌/패닝 + 토글 - GridGuide: 삭제 격자 셀 구조: ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │1,1│2,1│3,1│4,1│5,1│6,1│7,1│8,1│9,1│10│11│12 │ ← col ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ │1,2│... │ └───┴───────────────────────────────────────────┘ ↑ row ``` --- ## [2026-02-05] v5 그리드 시스템 완전 통합 ### 배경 (왜 v5로 전환했는가) **문제 상황**: - v4 Flexbox로 반응형 구현 시도 → 배치가 예측 불가능 - 캔버스에 "그리듯이" 배치하면 화면 크기별로 깨짐 **상급자 피드백**: > "이런 식이면 나중에 문제가 생긴다." > "스크린의 픽셀 규격과 마진 간격 규칙을 설정해라. > 큰 화면 디자인의 전체 프레임 규격과 사이즈 간격 규칙을 정한 다음에 > 거기에 컴포넌트를 끼워 맞추듯 우리의 규칙 내로 움직이게 바탕을 잡아라." **연구 내용**: - Softr: 블록 기반, 제약 기반 레이아웃 - Ant Design: 24열 그리드, 8px 간격 - Material Design: 4/8/12열, 반응형 브레이크포인트 **결정**: CSS Grid 기반 그리드 시스템 (v5) 채택 → 상세: [decisions/003-v5-grid-system.md](./decisions/003-v5-grid-system.md) ### popdocs 문서 구조 재정비 **배경**: 문서가 AI 에이전트 진입점 역할을 못함, 컨텍스트 효율화 필요 **적용 기법**: Progressive Disclosure (점진적 공개), Token as Currency **추가된 파일**: - `SAVE_RULES.md`: AI 저장/조회 규칙, 템플릿 - `STATUS.md`: 현재 진행 상태, 중단점 - `PROBLEMS.md`: 문제-해결 색인 - `INDEX.md`: 기능별 색인 - `sessions/`: 날짜별 작업 기록 **문서 계층**: - Layer 1 (진입점): README, STATUS, SAVE_RULES - Layer 2 (상세): CHANGELOG, PROBLEMS, INDEX, FILES, ARCHITECTURE - Layer 3 (심화): decisions/, sessions/, archive/ ### Breaking Changes - **v1, v2, v3, v4 레이아웃 완전 삭제** - 기존 POP 화면 데이터 전체 초기화 필요 - 레거시 컴포넌트 및 타입 삭제 ### Added - **CSS Grid 기반 그리드 시스템 (v5)** - 4개 모드별 칸 수: 4/6/8/12칸 - 명시적 위치 지정 (col, row, colSpan, rowSpan) - 모드별 오버라이드 지원 - 자동 위치 변환 (12칸 기준 → 다른 모드) - **통합된 파일 구조** - `PopCanvas.tsx`: 그리드 캔버스 (DnD + 줌 + 모드 전환) - `PopRenderer.tsx`: 그리드 렌더링 - `ComponentEditorPanel.tsx`: 속성 편집 - `pop-layout.ts`: v5 전용 타입 정의 - `gridUtils.ts`: 그리드 유틸리티 함수 ### Removed - `PopCanvasV4.tsx`, `PopCanvas.tsx (v3)` - `PopFlexRenderer.tsx`, `PopLayoutRenderer.tsx` - `ComponentEditorPanelV4.tsx`, `PopPanel.tsx` - v1, v2, v3, v4 타입 정의 및 유틸리티 함수 - `test-v4` 테스트 페이지 ### Changed - `screenManagementService.ts`: v5 전용으로 단순화 - `screen_layouts_pop` 테이블: 기존 데이터 삭제, v5 전용 - `PopDesigner.tsx`: v5 전용으로 리팩토링 - 뷰어 페이지: v5 렌더러 전용 ### Technical Details ```typescript // v5 그리드 모드 type GridMode = "mobile_portrait" | "mobile_landscape" | "tablet_portrait" | "tablet_landscape"; // 그리드 설정 const GRID_BREAKPOINTS = { mobile_portrait: { columns: 4, rowHeight: 48, gap: 8, padding: 12 }, mobile_landscape: { columns: 6, rowHeight: 44, gap: 8, padding: 16 }, tablet_portrait: { columns: 8, rowHeight: 52, gap: 12, padding: 20 }, tablet_landscape: { columns: 12, rowHeight: 56, gap: 12, padding: 24 }, }; // 컴포넌트 위치 interface PopGridPosition { col: number; // 시작 열 (1부터) row: number; // 시작 행 (1부터) colSpan: number; // 열 크기 rowSpan: number; // 행 크기 } ``` --- ## [2026-02-04] Phase 2.1 완료 - 배치 고정 기능 ### Added - **현재 모드 추적** (PopDesigner.tsx) - `currentViewportMode` 상태 추가 - PopCanvasV4와 양방향 동기화 - 모드 변경 시 자동 업데이트 - **배치 고정 기능** - "고정" 버튼 추가 (기본 모드 제외) - `handleLockLayoutV4()` - 현재 배치를 오버라이드에 저장 - 배치 정보: direction, wrap, gap, alignItems, justifyContent, children 순서 - **오버라이드 초기화 기능** - `handleResetOverrideV4()` - 오버라이드 삭제 - "자동으로 되돌리기" 버튼 (편집된 모드만 표시) - 자동 계산으로 되돌림 ### Changed - **PopCanvasV4 Props 구조 변경** - `currentMode` prop 추가 (외부에서 제어) - `onModeChange` 콜백 추가 - `onLockLayout` 콜백 추가 - 내부 `activeViewport` 상태 제거 (부모가 관리) - **프리셋 버튼 동작** - 클릭 시 부모 상태 업데이트 (`onModeChange`) - `currentMode` prop 기반으로 활성 상태 표시 ### Technical Details ```typescript // 고정 로직 const handleLockLayoutV4 = () => { const newLayout = { ...layoutV4, overrides: { ...layoutV4.overrides, [currentViewportMode]: { containers: { root: { direction: layoutV4.root.direction, wrap: layoutV4.root.wrap, gap: layoutV4.root.gap, children: layoutV4.root.children, // 순서 고정 // ... 기타 배치 속성 } } } } }; }; // 초기화 로직 const handleResetOverrideV4 = (mode) => { const newOverrides = { ...layoutV4.overrides }; delete newOverrides[mode]; // overrides가 비면 undefined로 설정 }; ``` ### UI 변경 ``` 툴바: [모바일↕] [모바일↔] [태블릿↕] [태블릿↔(기본)] [고정] [자동으로 되돌리기] 조건부 표시: - "고정" 버튼: 기본 모드가 아닐 때 - "자동으로 되돌리기": 오버라이드가 있을 때 ``` ### 주의사항 - 크기는 고정하지 않음 (여전히 자동 스케일링) - 배치만 오버라이드 (순서, 방향, 정렬) - 최소/최대값 기능은 별도 구현 필요 --- ## [2026-02-04] Phase 2 시작 - 오버라이드 UI 표시 ### Added - **오버라이드 데이터 구조** (pop-layout.ts) - `PopModeOverride` 인터페이스 추가 - `PopLayoutDataV4.overrides` 필드 추가 - 3개 모드 오버라이드 지원 (mobile_portrait, mobile_landscape, tablet_portrait) - **프리셋 버튼 상태 표시** (PopCanvasV4.tsx) - 기본 모드: "(기본)" 텍스트 표시 - 편집된 모드: "(편집)" 텍스트 + 노란색 강조 - 자동 모드: 기본 스타일 ### Changed - **hasOverride 함수 구현** - `layout.overrides` 필드 체크 - 컴포넌트/컨테이너 오버라이드 존재 여부 확인 --- ## [2026-02-04] 비율 스케일링 시스템 구현 ### Added - **비율 스케일링 시스템** (업계 표준 Scale with Fixed Aspect Ratio) - 기준 너비: 1024px (10인치 태블릿 가로) - 최대 너비: 1366px (12인치 태블릿) - 8~12인치 화면에서 배치 유지, 크기만 비례 조정 - **뷰포트 감지** (page.tsx) - `viewportWidth` state 추가 - resize 이벤트 리스너로 실시간 감지 - `Math.min(window.innerWidth, 1366)` 최대값 제한 ### Changed - **PopFlexRenderer.tsx** - `BASE_VIEWPORT_WIDTH = 1024` 상수 추가 - `scale = viewportWidth / BASE_VIEWPORT_WIDTH` 계산 - `calculateSizeStyle()` 함수에 scale 파라미터 추가 - 컴포넌트 크기 (fixedWidth, fixedHeight) 스케일 적용 - 컨테이너 gap, padding 스케일 적용 - 디자인 모드: scale = 1 (원본), 뷰어 모드: 실제 scale 적용 - **ComponentRendererV4** - `viewportWidth` prop 추가 - 내부에서 scale 계산하여 sizeStyle에 적용 - **ContainerRenderer** - scaledGap, scaledPadding 계산하여 containerStyle에 적용 ### Technical Details ``` 비율 스케일링 계산: scale = 실제 화면 너비 / 기준 너비 (1024px) 예시: - 800px (8인치): scale = 0.78 → 200px 컴포넌트 → 156px - 1024px (10인치): scale = 1.00 → 200px 컴포넌트 → 200px (기준) - 1366px (12인치): scale = 1.33 → 200px 컴포넌트 → 266px - 1920px (데스크톱): max-width 1366px 적용 → 12인치와 동일 + 여백 ``` ### Fixed - **DndProvider 에러** (뷰어 페이지) - 원인: isDesignMode=false일 때 useDrag/useDrop 훅 호출 - 해결: DraggableComponentWrapper에서 isDesignMode 체크 후 early return --- ## [2026-02-04] Flexbox 가로 배치 + Spacer + Undo/Redo 개선 ### Added - **Spacer 컴포넌트** (`pop-spacer`) - 빈 공간을 차지하여 레이아웃 정렬에 사용 - 기본 크기: `width: fill`, `height: 48px` - 디자인 모드에서 점선 배경으로 표시 - 실제 모드에서는 투명 (공간만 차지) - **컴포넌트 순서 변경 (드래그 앤 드롭)** - 같은 컨테이너 내에서 컴포넌트 순서 변경 가능 - 드래그 중인 컴포넌트는 반투명하게 표시 - 드롭 위치는 파란색 테두리로 표시 - `handleReorderComponentV4` 핸들러 추가 ### Changed - **기본 레이아웃 방향 변경** (Flexbox 가로 배치) - `direction: "vertical"` → `direction: "horizontal"` - `wrap: false` → `wrap: true` (자동 줄바꿈) - `alignItems: "stretch"` → `alignItems: "start"` - 컴포넌트가 가로로 나열되고, 공간 부족 시 다음 줄로 이동 - **컴포넌트 기본 크기 타입별 설정** - 필드: 200x48px (fixed) - 버튼: 120x48px (fixed) - 리스트: fill x 200px - 인디케이터: 120x80px (fixed) - 스캐너: 200x48px (fixed) - 숫자패드: 200x280px (fixed) - Spacer: fill x 48px - **Undo/Redo 방식 개선** (데스크탑 모드와 동일) - `useLayoutHistory` 훅 제거 - 별도 `history[]`, `historyIndex` 상태로 관리 - `saveToHistoryV4()` 함수로 명시적 히스토리 저장 - 컴포넌트 추가/삭제/수정/순서변경 시 히스토리 저장 - **디바이스 스크린 스크롤** - `overflow: auto` 추가 (컴포넌트가 넘치면 스크롤) - `height` → `minHeight` 변경 (컨텐츠에 따라 높이 증가) ### Technical Details ``` 업계 표준 레이아웃 방식 (Figma, Webflow, FlutterFlow): 1. Flexbox 기반 Row/Column 배치 2. 크기 제어: Fill / Fixed / Hug 3. Spacer 컴포넌트로 정렬 조정 4. 화면 크기별 조건 분기 (반응형) 사용 예시: [버튼A] [Spacer(fill)] [버튼B] → 버튼B가 오른쪽 끝으로 [Spacer] [컴포넌트] [Spacer] → 컴포넌트가 가운데로 ``` --- ## [2026-02-04] 드래그 리사이즈 + Undo/Redo 기능 ### Added - **useLayoutHistory.ts** - Undo/Redo 히스토리 훅 - 최대 50개 히스토리 저장 - `undo()`, `redo()`, `canUndo`, `canRedo` - `reset()` - 새 레이아웃 로드 시 히스토리 초기화 - **드래그 리사이즈 핸들** (PopFlexRenderer) - 오른쪽 핸들: 너비 조정 (cursor: ew-resize) - 아래쪽 핸들: 높이 조정 (cursor: ns-resize) - 오른쪽 아래 핸들: 너비+높이 동시 조정 (cursor: nwse-resize) - 선택된 컴포넌트에만 표시 - 최소 크기 보장 (너비 48px, 높이 touchTargetMin) ### Changed - **PopDesigner.tsx** - `useLayoutHistory` 훅 통합 (v3, v4 각각 독립적) - Undo/Redo 버튼 추가 (툴바 오른쪽) - 단축키 등록: - `Ctrl+Z` / `Cmd+Z`: 실행 취소 - `Ctrl+Shift+Z` / `Cmd+Shift+Z` / `Ctrl+Y`: 다시 실행 - `handleResizeComponentV4` 핸들러 추가 - **PopCanvasV4.tsx** - `onResizeComponent` prop 추가 - PopFlexRenderer에 전달 - **PopFlexRenderer.tsx** - `onComponentResize` prop 추가 - ComponentRendererV4에 리사이즈 핸들 추가 - 드래그 이벤트 처리 (mousemove, mouseup) ### 단축키 목록 | 단축키 | 기능 | |--------|------| | `Delete` / `Backspace` | 선택된 컴포넌트 삭제 | | `Ctrl+Z` / `Cmd+Z` | 실행 취소 (Undo) | | `Ctrl+Shift+Z` / `Ctrl+Y` | 다시 실행 (Redo) | | `Space` + 드래그 | 캔버스 패닝 | | `Ctrl` + 휠 | 줌 인/아웃 | --- ## [2026-02-04] v4 통합 설계 모드 Phase 1 완료 ### 목표 v4를 기본 레이아웃 모드로 통합하고, 새 화면은 자동으로 v4로 시작 ### Added - **ComponentPaletteV4.tsx** - v4 전용 컴포넌트 팔레트 - 6개 컴포넌트 (필드, 버튼, 리스트, 인디케이터, 스캐너, 숫자패드) - 드래그 앤 드롭 지원 ### Changed - **PopDesigner.tsx** - v3/v4 통합 디자이너로 리팩토링 - v3/v4 탭 제거 (자동 판별) - 새 화면 → v4로 시작 - 기존 v3 화면 → v3로 로드 (하위 호환) - 빈 레이아웃 → v4로 시작 (컴포넌트 유무로 판별) - 레이아웃 버전 텍스트 표시 ("자동 레이아웃 (v4)" / "4모드 레이아웃 (v3)") - **PopCanvasV4.tsx** - 4개 프리셋으로 변경 - 기존: [모바일] [태블릿] [데스크톱] - 변경: [모바일↕] [모바일↔] [태블릿↕] [태블릿↔] - 기본 프리셋: 태블릿 가로 (1024x768) - 슬라이더 범위: 320~1200px - 비율 유지: 슬라이더 조절 시 높이도 비율에 맞게 자동 조정 ### Fixed - 새 화면이 v3로 열리는 문제 - 원인: 백엔드가 빈 v2 레이아웃 반환 (version 필드 있음) - 해결: 컴포넌트 유무로 빈 레이아웃 판별 → v4로 시작 ### Technical Details ``` 레이아웃 로드 로직: 1. version 필드 확인 2. components 존재 여부 확인 3. version 있고 components 있음 → 해당 버전으로 로드 4. version 없거나 components 없음 → v4로 새로 시작 ``` --- ## [2026-02-04] Phase 2.1 완료 - 배치 고정 기능 (버그 수정) ### 🔥 주요 버그 수정 - **layoutV4.root 오염 문제 해결**: 다른 모드에서 편집 시 기본 레이아웃이 변경되던 버그 수정 - **tempLayout 도입**: 고정 전 임시 배치를 별도 상태로 관리하여 root를 보호 - **렌더러 병합 로직**: `PopFlexRenderer`에 오버라이드 자동 병합 기능 추가 ### 데이터 흐름 개선 1. **기본 모드 (태블릿 가로)** - 드래그/속성 변경 → `layoutV4.root` 직접 수정 ✅ - 모든 다른 모드의 기본값으로 사용 2. **다른 모드 (모바일 세로 등)** - 드래그 → `tempLayout` 임시 저장 (화면에만 표시) - "고정" 버튼 → `layoutV4.overrides[mode]`에 저장 - 속성 패널 → 비활성화 + 안내 메시지 3. **렌더링** - `tempLayout` 있으면 최우선 표시 (고정 전 미리보기) - 오버라이드 있으면 `root`와 병합 - 없으면 `root` 그대로 표시 ### 수정 파일 - `PopDesigner.tsx`: tempLayout 상태 추가, 핸들러 수정 - `PopFlexRenderer.tsx`: 병합 로직 추가 (getMergedRoot) - `PopCanvasV4.tsx`: tempLayout props 전달 - `ComponentEditorPanelV4.tsx`: 속성 패널 비활성화 로직 --- ## [2026-02-04] Phase 3 완료 - visibility + 줄바꿈 컴포넌트 ### 추가 기능 - **visibility 속성**: 모드별 컴포넌트 표시/숨김 제어 - **pop-break 컴포넌트**: 강제 줄바꿈 (flex-basis: 100%) - **컴포넌트 오버라이드 병합**: 모드별 컴포넌트 설정 변경 가능 ### 타입 정의 ```typescript interface PopComponentDefinitionV4 { // 기존 속성... // 🆕 모드별 표시/숨김 visibility?: { tablet_landscape?: boolean; tablet_portrait?: boolean; mobile_landscape?: boolean; mobile_portrait?: boolean; }; } // 🆕 줄바꿈 컴포넌트 type PopComponentType = | "pop-field" | "pop-button" | "pop-list" | "pop-indicator" | "pop-scanner" | "pop-numpad" | "pop-spacer" | "pop-break"; // 새로 추가 ``` ### 렌더러 개선 - `isComponentVisible()`: visibility 체크 로직 - `getMergedComponent()`: 컴포넌트 오버라이드 병합 - pop-break 전용 렌더링 (디자인 모드: 점선, 실제: 높이 0) ### 삭제 함수 개선 - `cleanupOverridesAfterDelete()`: 컴포넌트 삭제 시 모든 오버라이드 정리 - containers.root.children 정리 - components 오버라이드 정리 - 빈 오버라이드 자동 제거 ### UI 개선 - 속성 패널에 "표시" 탭 추가 (Eye 아이콘) - 모드별 체크박스 UI - 반응형 숨김 (hideBelow) 유지 - 팔레트에 "줄바꿈" 컴포넌트 추가 ### 사용 예시 ``` 태블릿 가로: [A] [B] [C] [D] [E] ← 한 줄 모바일 세로: [A] [B] ─────── ← 줄바꿈 (visibility: mobile만 true) [C] [D] [E] ``` ### 수정 파일 - `pop-layout.ts`: 타입 추가, 삭제 함수 수정 - `PopFlexRenderer.tsx`: visibility, 병합, pop-break 렌더링 - `ComponentEditorPanelV4.tsx`: 표시 탭 추가 - `ComponentPaletteV4.tsx`: 줄바꿈 추가 --- ## [2026-02-04] v4 타입 및 렌더러 ### Added - **v4 타입 정의** (간결 버전) - `PopLayoutDataV4` - 단일 소스 레이아웃 - `PopContainerV4` - 스택 컨테이너 (direction, wrap, gap, alignItems) - `PopComponentDefinitionV4` - 크기 제약 기반 (size: fixed/fill/hug) - `PopSizeConstraintV4` - 크기 규칙 - `PopResponsiveRuleV4` - 반응형 규칙 (breakpoint별 변경) - `PopGlobalSettingsV4` - 전역 설정 - `createEmptyPopLayoutV4()` - 생성 함수 - `isV4Layout()` - 타입 가드 - CRUD 함수들 (add, remove, update, find) - **PopFlexRenderer.tsx** - v4 Flexbox 렌더러 - 컨테이너 재귀 렌더링 - 반응형 규칙 적용 - 크기 제약 → CSS 변환 - **ComponentEditorPanelV4.tsx** - v4 속성 편집 패널 - 크기 제약 편집 UI - 컨테이너 설정 UI - **PopCanvasV4.tsx** - v4 전용 캔버스 - 뷰포트 프리셋 - 너비 슬라이더 - 줌/패닝 --- ## [2026-02-04] (earlier) ### Added - 저장/조회 시스템 구축 - rangraph: AI 장기 기억 (시맨틱 검색, 요약) - popdocs: 상세 기록 (파일 기반, 히스토리) - 이중 저장 체계로 검색 + 기록 분리 ### Changed - popdocs 문서 구조 정리 - README.md: 저장/조회 규칙 추가 - 기존 문서 archive/로 이동 - 문서 관리 전략 확정 - 저장 시: 파일 형식 자동 파악 → 형식 맞춰 추가 → rangraph 요약 - 조회 시: rangraph 시맨틱 검색 ### Removed - .cursorrules 변경 계획 철회 (Git 커밋 영향) --- ## [2026-02-03] ### Added - v4 제약조건 기반 레이아웃 계획 - 단일 소스 + 자동 적응 - 3가지 규칙 (크기, 배치, 반응형) - ADR: `decisions/001-v4-constraint-based.md` --- ## [2026-02-02] ### Fixed - 캔버스 rowSpan 문제 - 원인: gridTemplateRows 고정 px - 해결: `1fr` 사용 --- ## [2026-02-01] ### Fixed - 4모드 자동 전환 문제 - 해결: useResponsiveMode 훅 추가 --- ## [2026-01-31] ### Added - v3 섹션 제거, 순수 그리드 구조 - 4개 모드 독립 그리드 --- ## [2026-01-30] ### Added - POP 디자이너 기본 구조 - PopDesigner, PopCanvas 컴포넌트 --- ## [2026-01-29] ### Added - screen_layouts_pop 테이블 - POP 레이아웃 API (CRUD) --- *최신이 위, 시간순 역순*