30 KiB
문제-해결 색인
용도: "이전에 비슷한 문제 어떻게 해결했어?" 검색 팁: Ctrl+F로 키워드 검색 (에러 메시지, 컴포넌트명 등)
렌더링 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| rowSpan이 적용 안됨 | gridTemplateRows를 1fr로 변경 |
2026-02-02 | grid, rowSpan, CSS |
| 컴포넌트 크기 스케일 안됨 | viewportWidth 기반 scale 계산 추가 | 2026-02-04 | scale, viewport, 반응형 |
| 그리드 가이드 셀 크기 불균일 | gridAutoRows → gridTemplateRows로 행 높이 강제 고정 | 2026-02-06 | gridAutoRows, gridTemplateRows, 셀 크기, CSS Grid |
| 컴포넌트 콘텐츠가 셀 경계 벗어남 | overflow-visible → overflow-hidden 변경 | 2026-02-06 | overflow, 셀 크기, 콘텐츠 |
| 뷰어에서 플레이스홀더만 표시 | page.tsx에 레지스트리 초기화 import 추가 + renderActualComponent() 실제 컴포넌트 렌더링으로 교체 | 2026-02-09 | 뷰어, 플레이스홀더, 레지스트리, side-effect import |
| 뷰어에서 스크롤 불가 (콘텐츠 잘림) | 최외곽 overflow-hidden 제거, overflow-auto 공통 적용, min-h-full 추가 | 2026-02-09 | 스크롤, overflow, h-screen, 뷰어 |
| 글자 크기 커스텀 vs @container 충돌 | 절대 px 글자 크기 전체 제거, @container 반응형 자동 유지 | 2026-02-11 | 글자 크기, @container, 반응형, overflow |
| .next 빌드 캐시 꼬임 (Docker) | docker-compose down -v + --build 재시작 |
2026-02-11 | .next, 캐시, Docker, 익명 볼륨, Turbopack |
상태 관리 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| stale closure (handleUpdateComponent) | setLayout(prev => ...) 함수적 업데이트, layout 의존성 제거 |
2026-02-11 | stale closure, useCallback, setState |
DnD (드래그앤드롭) 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| useDrag 에러 (뷰어에서) | isDesignMode 체크 후 early return | 2026-02-04 | DnD, useDrag, 뷰어 |
| DndProvider 중복 에러 | 최상위에서만 Provider 사용 | 2026-02-04 | DndProvider, react-dnd |
| Expected drag drop context (뷰어) | isDesignMode=false일 때 DraggableComponent 대신 일반 div 렌더링 | 2026-02-05 | DndProvider, useDrag, 뷰어, context |
| 컴포넌트 중첩(겹침) | toast import 누락 → sonner에서 import |
2026-02-05 | 겹침, overlap, toast |
| 리사이즈 핸들 작동 안됨 | useDrop 2개 중복 → 단일 useDrop으로 통합 | 2026-02-05 | resize, 핸들, useDrop |
| 드래그 좌표 완전 틀림 (Row 92) | 캔버스 scale 보정 누락 → (offset - rect.left) / scale |
2026-02-05 | scale, 좌표, transform |
| DND 타입 상수 불일치 | 3개 파일에 중복 정의 → constants/dnd.ts로 통합 |
2026-02-05 | 상수, DND, 타입 |
| 컴포넌트 이동 안됨 | useDrop accept 타입 불일치 → 공통 상수 사용 | 2026-02-05 | 이동, useDrop, accept |
타입 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| 인터페이스 이름 불일치 | V5 접미사 제거, 통일 | 2026-02-05 | 타입, interface, Props |
| v3/v4 타입 혼재 | v5 전용으로 통합, 레거시 삭제 | 2026-02-05 | 버전, 타입, 마이그레이션 |
레이아웃 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| 화면 밖 컴포넌트 정보 손실 | 자동 줄바꿈 로직 추가 (col > maxCol → col=1, row=맨아래+1) | 2026-02-06 | 자동배치, 줄바꿈, 정보손실 |
| Flexbox 배치 예측 불가 | CSS Grid로 전환 (v5) | 2026-02-05 | Flexbox, Grid, 반응형 |
| 4모드 각각 배치 힘듦 | 제약조건 기반 시스템 (v4) | 2026-02-03 | 모드, 반응형, 제약조건 |
| 4모드 자동 전환 안됨 | useResponsiveMode 훅 추가 | 2026-02-01 | 모드, 훅, 반응형 |
브레이크포인트/반응형 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| 뷰어 반응형 모드 불일치 | detectGridMode() 사용으로 일관성 확보 | 2026-02-06 | 반응형, 뷰어, 모드 |
| 768~839px 모드 불일치 | TABLET_MIN 768로 변경, 브레이크포인트 재설계 | 2026-02-06 | 브레이크포인트, 768px |
| useResponsiveMode vs GRID_BREAKPOINTS 불일치 | 뷰어에서 detectGridMode(viewportWidth) 사용 | 2026-02-06 | 훅, 상수, 일관성 |
저장/로드 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| 레이아웃 버전 충돌 | isV5Layout 타입 가드로 분기 | 2026-02-05 | 버전, 로드, 타입가드 |
| 빈 레이아웃 판별 실패 | components 존재 여부로 판별 | 2026-02-04 | 빈 레이아웃, 로드 |
UI/UX 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| root 레이아웃 오염 | tempLayout 도입 (임시 상태 분리) | 2026-02-04 | tempLayout, 상태, 오염 |
| 속성 패널 다른 모드 수정 | isDefaultMode 체크로 비활성화 | 2026-02-04 | 속성패널, 모드, 비활성화 |
브랜치 병합 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| ksh-dashboard + ksh-v2-work 병합 시 7파일 17지점 충돌 | 의존성 순서로 해결: 타입 - 등록 - 컴포넌트 - 렌더러 - 패널. 양쪽 기능 통합(union) 전략 | 2026-02-11 | 병합, merge, 충돌, pop-icon, pop-dashboard |
| PopComponentType 확장 충돌 | 양쪽 타입 union (pop-icon + pop-dashboard) 결합, DEFAULT_COMPONENT_GRID_SIZE에 양쪽 항목 추가 |
2026-02-11 | PopComponentType, union type, Record |
| COMPONENT_TYPE_LABELS 불완전 | PopRenderer(Record<PopComponentType, string>)에 4개 항목 모두 추가하여 타입 안전성 확보 |
2026-02-11 | COMPONENT_TYPE_LABELS, Record, 타입 안전 |
| isRealtime useEffect 충돌 (pop-text) | ksh-dashboard 로직 채택 - isRealtime 조건부 interval로 불필요한 timer 방지 |
2026-02-11 | isRealtime, useEffect, setInterval, pop-text |
| CSS 클래스 충돌 (ComponentEditorPanel Tabs) | ksh-v2-work의 방어적 CSS 채택 (overflow-hidden, min-h-0, m-0) - 스크롤 동작 안정성 우선 |
2026-02-11 | CSS, overflow, Tabs, TabsContent, 스크롤 |
| 레지스트리 패턴에서 불필요 props 전달 | 버그 아님 - React.ComponentType<any> 타입이므로 extra props 자동 무시. 레지스트리 아키텍처 의도적 패턴 |
2026-02-11 | 레지스트리, props, ComponentType, any |
그리드 가이드 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| SVG 격자와 CSS Grid 좌표 불일치 | GridGuide.tsx 삭제, PopRenderer에서 CSS Grid 셀로 격자 렌더링 | 2026-02-05 | 격자, SVG, CSS Grid, 좌표 |
| 행/열 라벨 위치 오류 | PopCanvas에 absolute positioning 라벨 추가 | 2026-02-05 | 라벨, 행, 열, 정렬 |
| 격자선과 컴포넌트 불일치 | 동일한 CSS Grid 좌표계 사용 | 2026-02-05 | 통합, 정렬, 일체감 |
뷰어 플레이스홀더 버그 상세 (2026-02-09)
증상
설계 화면(디자이너)에서는 이미지/텍스트/타이틀 정상 표시.
뷰어(/pop/screens/4114) 접속 시 "pop-text 1", "pop-text 2" 같은 라벨만 보임.
원인 (2가지)
원인 A: renderActualComponent() 함수가 하드코딩된 플레이스홀더만 반환
// 변경 전 (PopRenderer.tsx 555-564)
function renderActualComponent(component) {
const typeLabel = COMPONENT_TYPE_LABELS[component.type];
return (
<div>
<span>{component.label || typeLabel}</span>
</div>
);
}
원인 B: 뷰어 page.tsx에서 PopComponentRegistry 초기화 import 누락
- 디자이너에서는
PopDesigner.tsx가 컴포넌트를 import하여 레지스트리 초기화됨 - 뷰어에서는 아무도 레지스트리를 초기화하지 않아 빈 상태
해결
// page.tsx - 레지스트리 초기화 (PopRenderer보다 먼저!)
import "@/lib/registry/pop-components";
import PopRenderer from "@/components/pop/designer/renderers/PopRenderer";
// PopRenderer.tsx - 실제 컴포넌트 렌더링
function renderActualComponent(component) {
const registeredComp = PopComponentRegistry.getComponent(component.type);
const ActualComp = registeredComp?.component;
if (ActualComp) {
return <ActualComp config={component.config} label={component.label} />;
}
// fallback: 플레이스홀더
}
교훈
side-effect import (
import "...")는 해당 레지스트리를 사용하는 컴포넌트 import보다 반드시 앞에 위치해야 한다. JavaScript 모듈은 import 선언 순서대로 실행되므로, 순서가 뒤바뀌면 빈 레지스트리를 참조할 수 있다. 새 POP 컴포넌트를 등록할 때, 뷰어 페이지에도 초기화 import가 있는지 반드시 확인.
해결 완료 (이번 세션)
| 문제 | 상태 | 해결 방법 |
|---|---|---|
| PopCanvas 타입 오류 | 해결 | 임시 타입 가드 추가 |
| 팔레트 UI 없음 | 해결 | ComponentPalette.tsx 신규 추가 |
| SVG 격자 좌표 불일치 | 해결 | CSS Grid 기반 통합 |
| 드래그 좌표 완전 틀림 | 해결 | scale 보정 + calcGridPosition 함수 |
| DND 타입 상수 불일치 | 해결 | constants/dnd.ts 통합 |
| 컴포넌트 이동 안됨 | 해결 | useDrop/useDrag 타입 통일 |
| 컴포넌트 중첩(겹침) | 해결 | toast import 추가 → 겹침 감지 로직 정상 작동 |
| 리사이즈 핸들 작동 안됨 | 해결 | useDrop 통합 (2개 → 1개) |
| 숨김 컴포넌트 드래그 안됨 | 해결 | handleMoveComponent에서 숨김 해제 + 위치 저장 단일 상태 업데이트 |
| 그리드 범위 초과 에러 | 해결 | adjustedCol 계산으로 드롭 위치 자동 조정 |
| Expected drag drop context (뷰어) | 해결 | isDesignMode=false일 때 일반 div 렌더링 |
| hiddenComponentIds 중복 정의 | 해결 | 중복 useMemo 제거 (라인 410-412) |
| 뷰어 반응형 모드 불일치 | 해결 | detectGridMode() 사용 |
| 그리드 가이드 셀 크기 불균일 | 해결 | gridTemplateRows로 행 높이 강제 고정 |
| Canvas vs Renderer 행 수 불일치 | 해결 | 숨김 필터 통일, 여유행 +3으로 통일 |
| 디버깅 console.log 잔존 | 해결 | reviewComponents 내 console.log 삭제 |
| 뷰어 실제 컴포넌트 렌더링 안 됨 | 해결 | 레지스트리 초기화 import + renderActualComponent 수정 |
드래그 좌표 버그 상세 (2026-02-05)
증상
- 컴포넌트를 아래로 드래그 → 위로 올라감
- Row 92 같은 비정상 좌표
- 드래그 이동/리사이즈 전혀 작동 안됨
원인
캔버스: transform: scale(0.8)
getBoundingClientRect() → 스케일 적용된 크기 (1024px → 819px)
getClientOffset() → 뷰포트 기준 실제 마우스 좌표
이 둘을 그대로 계산하면 좌표 완전 틀림
해결
// 스케일 보정된 상대 좌표 계산
const relX = (offset.x - canvasRect.left) / canvasScale;
const relY = (offset.y - canvasRect.top) / canvasScale;
// 실제 캔버스 크기로 그리드 계산
calcGridPosition(relX, relY, customWidth, ...);
교훈
CSS
transform: scale()적용된 요소에서 좌표 계산 시,getBoundingClientRect()는 스케일 적용된 값을 반환하지만 마우스 좌표는 뷰포트 기준이므로 반드시 스케일 보정 필요
Expected drag drop context 에러 상세 (2026-02-05 심야)
증상
Invariant Violation: Expected drag drop context
at useDrag (...)
at DraggableComponent (...)
뷰어 페이지(/pop/viewer/[screenId])에서 POP 화면 조회 시 에러 발생
원인
PopRenderer의 DraggableComponent에서 useDrag 훅을 무조건 호출
→ 뷰어 페이지에는 DndProvider가 없음
→ React 훅은 조건부 호출 불가 (Rules of Hooks)
→ DndProvider 없이 useDrag 호출 시 context 에러
해결
// PopRenderer.tsx - 컴포넌트 렌더링 부분
if (isDesignMode) {
return (
<DraggableComponent ... /> // useDrag 사용
);
}
// 뷰어 모드: 드래그 없는 일반 렌더링
return (
<div className="..." style={positionStyle}>
<ComponentContent ... />
</div>
);
교훈
React DnD의
useDrag/useDrop훅은 반드시DndProvider내부에서만 호출해야 함. 디자인 모드와 뷰어 모드를 분기할 때, 훅이 포함된 컴포넌트 자체를 조건부 렌더링해야 함. 훅 내부에서canDrag: false로 설정해도 훅 자체는 호출되므로 context 에러 발생.
관련 파일
gridUtils.ts: convertAndResolvePositions(), needsReview()PopCanvas.tsx: ReviewPanel, ReviewItemPopRenderer.tsx: 자동 배치 위치 렌더링
뷰어 반응형 모드 불일치 상세 (2026-02-06)
증상
- 아이폰 SE, iPad Pro 프리셋은 정상 작동
- 브라우저 수동 리사이즈 시 6칸 모드(mobile_landscape)가 적용 안 됨
- 768~839px 구간에서 8칸으로 표시됨 (예상: 6칸)
원인
useResponsiveMode 훅:
- deviceType: width/height 비율로 "mobile"/"tablet" 판정
- isLandscape: width > height로 판정
- BREAKPOINTS.TABLET_MIN = 840 (당시)
GRID_BREAKPOINTS:
- mobile_landscape: 600~839px (6칸)
- tablet_portrait: 840~1023px (8칸)
결과:
- 768px 화면 → useResponsiveMode: "tablet" (768 < 840이지만 비율 판정)
- 768px 화면 → GRID_BREAKPOINTS: "mobile_landscape" (6칸)
- → 모드 불일치!
해결
1단계: 브레이크포인트 재설계
// 기존
mobile_landscape: { minWidth: 600, maxWidth: 839 }
tablet_portrait: { minWidth: 840, maxWidth: 1023 }
// 변경 후
mobile_landscape: { minWidth: 480, maxWidth: 767 }
tablet_portrait: { minWidth: 768, maxWidth: 1023 }
2단계: 훅 연동
// useDeviceOrientation.ts
BREAKPOINTS.TABLET_MIN: 768 // was 840
3단계: 뷰어 모드 감지 방식 변경
// page.tsx (뷰어)
const currentModeKey = isPreviewMode
? getModeKey(deviceType, isLandscape) // 프리뷰: 수동 선택
: detectGridMode(viewportWidth); // 일반: 너비 기반 (일관성 확보)
교훈
반응형 모드 판정은 **단일 소스(GRID_BREAKPOINTS)**를 기준으로 해야 함. 훅과 상수가 각각 다른 기준을 사용하면 구간별 불일치 발생. 뷰어에서는
detectGridMode(viewportWidth)직접 사용으로 일관성 확보.
뷰어 렌더링 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| 뷰어에서 실제 컴포넌트 대신 플레이스홀더 표시 | (1) page.tsx에 레지스트리 초기화 import 추가 (2) renderActualComponent()에서 PopComponentRegistry.getComponent() 조회 후 실제 렌더링 |
2026-02-09 | 뷰어, 플레이스홀더, 레지스트리, import, renderActualComponent |
| datetime 실시간 업데이트 기본값 불일치 | 미해결 - DateTimeDisplay의 if (!config?.isRealtime) return에서 undefined가 false로 평가되어 타이머 미작동. 설정 패널은 ?? true로 기본 켜짐 표시. 권장: const isRealtime = config?.isRealtime ?? true |
2026-02-09 | datetime, isRealtime, undefined, 기본값, 타이머 |
병합 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| ScreenDesigner.tsx 3건 충돌 (origin/main 병합) | 함수 시그니처: ksh-v2-work 유지(isPop/defaultDevicePreview), 저장 로직: 3단계 분기 유지+console.log 제거, 툴바 props: origin/main 채택 | 2026-02-09 | 병합, merge, ScreenDesigner, 충돌 |
| usePanelState 중복 선언 (병합 시 발견) | 충돌 1 해결 과정에서 L175의 중복 usePanelState 제거, L215의 완전한 버전만 유지 | 2026-02-09 | usePanelState, 중복, 병합 |
| 툴바 JSX 들여쓰기 불일치 (병합 후 린트) | origin/main 코드가 ksh-v2-work와 2칸 들여쓰기 차이. 기능 영향 없음. 추후 포매팅 정리 권장 | 2026-02-09 | 들여쓰기, 포매팅, 린트, prettier |
컴포넌트 등록 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| pop-dashboard 팔레트 미노출 | PopComponentType/PALETTE_ITEMS/DEFAULT_COMPONENT_GRID_SIZE/COMPONENT_TYPE_LABELS 4곳에 수동 등록 누락. 4곳 모두 추가 | 2026-02-10 | 팔레트, 등록, PopComponentType, 하드코딩, 레지스트리 |
| 미사용 import (AggregatedResult) | PopDashboardComponent.tsx에서 type AggregatedResult import 제거 | 2026-02-10 | import, 미사용, 코드 품질 |
pop-dashboard 데이터/SQL 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| 대상 컬럼 조회 안 됨 | fetchTableColumns에서 tableManagementApi(axios) 우선 사용, dashboardApi(fetch) 폴백 | 2026-02-10 | 컬럼, schema, API, 인증, fetch, axios |
| SUM()/COUNT() 빈 괄호 SQL | validateDataSourceConfig으로 중간 상태 검증, 미완료 시 SQL 생성 차단 | 2026-02-10 | SQL, 집계, validate, 중간상태, 백엔드 |
| API 30초 타임아웃 (auth/me 등) | 잘못된 SQL이 백엔드 과부하 유발 -> SQL 차단 + 브라우저 새로고침 | 2026-02-10 | timeout, auth, 과부하, 브라우저 멈춤 |
| Docker unhealthy (curl 미설치) | healthcheck에 curl이 없어 false 양성. 서비스 자체는 정상. 확인만 필요 | 2026-02-10 | docker, healthcheck, curl, unhealthy |
pop-dashboard 렌더링 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| 2열 설정이 1열로 렌더링 | GridMode.tsx MIN_CELL_WIDTH 160->80. 초기 containerWidth=300에서 (300-8)/2=146 < 160이라 축소됨 | 2026-02-10 | 그리드, 열, 레이아웃, MIN_CELL_WIDTH, containerWidth |
| 라벨/단위/증감율 잘림 | 4개 아이템에서 truncate/hidden 제거, text-[10px]->text-xs, p-2->p-3 | 2026-02-10 | truncate, hidden, 라벨, 잘림, @container |
| useEffect 불필요 데이터 재호출 | visibleItems 배열 참조 -> visibleItemIds(JSON 문자열) 의존성 안정화 | 2026-02-10 | useEffect, 의존성, 배열참조, JSON.stringify |
| 파이 차트 미표시 | (1) fetch 기반 API가 iframe에서 간헐 실패 -> apiClient(axios) 우선 (2) PostgreSQL bigint 문자열 -> Number() 변환 | 2026-02-10 | PieChart, fetch, axios, bigint, 문자열, Number |
| 파이 차트 라벨/레전드 없음 | ChartItem.tsx에 Legend 컴포넌트 + custom label 포맷(name value (percent%)) 추가 |
2026-02-10 | PieChart, Legend, label, Recharts |
| 게이지 가로 레이아웃 비율 깨짐 | max-w-[200px] 고정 -> h-full w-auto max-w-full 높이 기반 스케일링. SVG 래퍼를 flex-1 min-h-0으로 변경 | 2026-02-10 | 게이지, SVG, 비율, max-width, 가로 레이아웃 |
| KPI/통계 카드 왼쪽 정렬 | KpiCard에 items-center, StatCard에 items-center justify-center 추가. 4개 아이템 정렬 통일 | 2026-02-10 | 정렬, items-center, KPI, StatCard |
pop-dashboard 설정 패널 관련
| 문제 | 해결 | 날짜 | 키워드 |
|---|---|---|---|
| 설정 패널 표시 모드/아이템 탭 클릭 시 에러 | ConfigPanelProps에서 onChange를 onUpdate로 변경. ComponentEditorPanel이 onUpdate로 전달하는데 대시보드만 onChange로 수신하여 TypeError 발생. onUpdate: onChange 구조분해로 해결 |
2026-02-10 | onChange, onUpdate, props, TypeError, 설정패널, 컨벤션 |
| gaugeConfig min/max/target 변경 안 됨 | 스프레드 연산자 순서 버그. { max: newValue, ...item.gaugeConfig } -> { ...item.gaugeConfig, max: newValue }. 나중에 오는 속성이 우선 |
2026-02-10 | 스프레드, 순서, gaugeConfig, 덮어쓰기, onChange |
| 차트 X축/Y축 입력 혼동 | "X축 컬럼"과 "그룹핑(X축)" 중복으로 사용자 혼동. 수동 입력 제거, 자동 설정 안내 텍스트로 교체 | 2026-02-10 | xAxisColumn, groupBy, 중복, UX, 설정패널 |
| React hooks 규칙 위반 (early return) | PopDashboardComponent.tsx에서 if (!config) early return이 hooks보다 앞에 위치. 모든 hooks 선언 이후로 early return 이동, visibleItems에 ?? [] 안전 처리 추가 |
2026-02-10 | hooks, early return, Rules of Hooks, useState, useEffect |
config가 빈 객체 {}로 전달되어 items undefined |
ComponentEditorPanel이 component.config || {}로 빈 객체 전달 -> ??가 빈 객체를 통과 -> cfg.items undefined -> .map(), .length, .filter() TypeError. ConfigPanel: {...DEFAULT_CONFIG, ...config} spread 병합, Preview: Array.isArray() 가드 추가, Component: Array.isArray() 가드 추가 |
2026-02-10 | config, 빈 객체, nullish coalescing, undefined, items, TypeError |
|| 설정 탭 세로 스크롤 불가 (페이지 추가 시 콘텐츠 잘림) | ComponentEditorPanel.tsx의 Tabs와 모든 TabsContent에 min-h-0 누락. Flexbox에서 flex 자식의 기본 min-height: auto가 콘텐츠 크기 이하로 축소를 막아 overflow-auto가 작동하지 않음. Tabs에 min-h-0, TabsList에 shrink-0, 4개 TabsContent에 min-h-0 추가로 해결 | 2026-02-10 | 스크롤, overflow, min-h-0, flex, TabsContent, 설정패널, 페이지 탭 |
설정 탭 스크롤 버그 상세 (2026-02-10)
증상
POP 디자이너에서 대시보드 컴포넌트 선택 -> 설정 탭 -> 페이지 탭에서 페이지를 3개 이상 추가하면 아래쪽 페이지가 잘려서 보이지 않음. 세로 스크롤 불가.
잘못된 접근 (실패 기록)
처음에 PopDashboardConfigPanel(자식)에서 문제를 해결하려고 시도:
- 외곽
div를flex h-full flex-col로 변경 - 탭 콘텐츠에
overflow-y-auto래퍼 추가
실패 이유: PopDashboardConfigPanel은 부모(ComponentSettingsForm)의 overflow-auto가 담당하는 스크롤 영역 안에 있으므로, 자식이 h-full로 높이를 채우면 부모의 스크롤 영역이 깨짐. 문제는 자식이 아니라 부모의 높이 제약이 제대로 전파되지 않는 것.
근본 원인
ResizablePanel (높이 확정)
└ ComponentEditorPanel: div.flex.h-full.flex-col
├ 헤더 (고정)
└ Tabs: flex.flex-1.flex-col ← min-h-0 누락!
├ TabsList (고정)
└ TabsContent: flex-1.overflow-auto ← min-h-0 누락!
└ ComponentSettingsForm
└ PopDashboardConfigPanel ← 콘텐츠가 길어짐
Flexbox의 min-height: auto 기본값 때문에:
Tabs(flex-1)가 콘텐츠에 의해 무한 확장TabsContent(overflow-auto)도 콘텐츠에 의해 확장 -> 스크롤 발생 안 함- 결과:
overflow-auto가 있지만 높이 제약이 없어 스크롤바 미생성
해결
// ComponentEditorPanel.tsx
// 변경 전
<Tabs defaultValue="position" className="flex flex-1 flex-col">
<TabsList className="w-full justify-start ...">
<TabsContent value="settings" className="flex-1 overflow-auto p-4">
// 변경 후
<Tabs defaultValue="position" className="flex min-h-0 flex-1 flex-col">
<TabsList className="w-full shrink-0 justify-start ...">
<TabsContent value="settings" className="min-h-0 flex-1 overflow-auto p-4">
교훈
Flexbox에서
overflow-auto가 작동하려면, 해당 요소와 모든 flex 조상에min-h-0(또는min-w-0)이 필요하다.flex-1만으로는 높이가 제한되지 않는다. flex 자식의 기본min-height: auto가 콘텐츠 크기 이하로 축소를 막기 때문이다. 스크롤 문제가 발생하면 자식 컴포넌트가 아니라 부모 flex 체인부터 위로 추적해야 한다.
폼 중간 상태 -> 잘못된 SQL -> 백엔드 장애 상세 (2026-02-10)
증상
브라우저 콘솔에 timeout of 30000ms exceeded 에러 반복. /auth/me, /auth/status, /admin/menus 등 핵심 API가 모두 30초 타임아웃. 브라우저가 멈추거나 자동 종료.
원인 체인
1. 대시보드 설정에서 집계 유형(SUM/AVG)만 선택, 대상 컬럼 미선택
↓
2. dataFetcher.ts의 buildAggregationSQL이 "SUM()" 같은 잘못된 SQL 생성
↓
3. 잘못된 SQL이 백엔드로 전송, PostgreSQL "function sum() does not exist" 에러
↓
4. refreshInterval(기본값)로 이 요청이 반복 전송
↓
5. 백엔드 과부하 -> Docker 컨테이너 unhealthy
↓
6. 인증/메뉴 등 다른 API도 30초 타임아웃
↓
7. 여러 API가 병렬로 30초씩 대기 -> 브라우저 멈춤
해결
// dataFetcher.ts - SQL 생성 전 필수값 검증
function validateDataSourceConfig(ds: DataSourceConfig): string | null {
if (!ds.tableName) return "테이블이 선택되지 않았습니다";
if (ds.aggregation?.type && ds.aggregation.type !== "COUNT" && !ds.aggregation.column) {
return "집계 대상 컬럼이 선택되지 않았습니다";
}
// ... 조인 검증 등
return null; // 유효
}
// fetchAggregatedData에서 호출
const validationError = validateDataSourceConfig(dataSource);
if (validationError) {
return { value: 0, error: validationError };
}
교훈
프론트엔드 설정 폼의 "중간 상태"(일부만 입력)가 백엔드에 전달되면 연쇄 장애로 이어질 수 있다. SQL/API 호출 전에 반드시
validate함수를 배치하고, 미완료 상태에서는 요청 자체를 차단해야 한다. 특히refreshInterval이 있는 자동 갱신 기능은 잘못된 요청을 반복 전송할 수 있어 더 위험하다.
그리드 2열 -> 1열 축소 상세 (2026-02-10)
증상
디자이너에서 페이지를 2열로 설정했는데, 실제 화면에서는 아이템이 세로로 쌓여 1열로 표시됨.
원인
GridModeComponent에서 반응형 열 축소 로직:
MIN_CELL_WIDTH = 160px
containerWidth = 300px (초기값, ResizeObserver 발동 전)
gap = 8px
cellWidth = (300 - 8) / 2 = 146px
146 < 160 → actualColumns = 1 (축소!)
ResizeObserver가 실제 너비를 반영해도,
초기 렌더링에서 이미 1열로 결정됨
해결
// GridMode.tsx
const MIN_CELL_WIDTH = 80; // was 160
// 80px이면 146 >= 80 → 2열 유지
교훈
반응형 열 축소 로직의 MIN_CELL_WIDTH는 실제 사용 시나리오를 고려해야 한다. ResizeObserver가 발동하기 전 초기 containerWidth 기본값이 작을 수 있으므로, MIN_CELL_WIDTH를 너무 크게 설정하면 의도치 않게 열이 축소된다.
stale closure (handleUpdateComponent)
| 항목 | 내용 |
|---|---|
| 문제 | PopDesigner.tsx의 handleUpdateComponent에서 layout state를 직접 참조하여, 빠른 연속 설정 변경 시 이전 state가 캡처되어 변경값 유실 |
| 증상 | 정렬 버튼을 빠르게 클릭하면 이전 클릭의 변경이 덮어씌워짐 |
| 원인 | useCallback의 의존성 배열에 layout이 있지만, layout 갱신 전에 다음 호출이 발생하면 이전 클로저가 사용됨 |
| 해결 | setLayout(prev => ...) 함수적 업데이트로 변경, 의존성에서 layout 제거 → [saveToHistory]만 유지 |
| 날짜 | 2026-02-11 |
| 키워드 | stale closure, useCallback, setState, 함수적 업데이트, 빠른 연속 변경 |
교훈
useCallback안에서 state를 직접 참조하면 빠른 연속 호출 시 이전 값이 캡처된다. 항상setState(prev => ...)패턴으로 최신 state에 접근해야 한다. 특히 사용자 인터랙션(버튼 클릭)이 빠르게 반복될 수 있는 곳에서 주의.
.next 빌드 캐시 꼬임 (Docker 익명 볼륨)
| 항목 | 내용 |
|---|---|
| 문제 | ChartItem.tsx를 수정했으나 브라우저에 반영되지 않음. 디버그 console.log도 콘솔에 나타나지 않음 |
| 증상 | 다른 컴포넌트(StatCard, Gauge)는 최신 코드 실행, 차트만 이전 코드 실행 |
| 원인 | Docker compose에서 /app/.next가 익명 볼륨으로 분리되어 호스트의 .next 삭제와 독립적. Turbopack 모듈 캐시가 특정 파일 변경을 인식하지 못함 |
| 해결 | docker-compose -f ... down -v (볼륨 포함 삭제) + docker-compose -f ... up -d --build (재빌드) |
| 날짜 | 2026-02-11 |
| 키워드 | .next, 캐시, Docker, 익명 볼륨, Turbopack, HMR, 빌드 |
교훈
Docker compose에서
/app/.next가 익명 볼륨인 경우, 호스트에서rm -rf .next를 해도 컨테이너 캐시에 영향 없음.docker-compose down -v로 볼륨까지 제거해야 캐시가 초기화됨. 파일 수정이 브라우저에 반영 안 되면: (1) 디버그 로그 추가 (2) 로그가 안 나오면 캐시 문제 의심 (3) 볼륨 포함 재빌드.
글자 크기 커스텀 vs @container 반응형 충돌
| 항목 | 내용 |
|---|---|
| 문제 | 글자 크기 3그룹(라벨/메인값/보조)을 절대 px(12~64px)로 설정하면, 유동적인 그리드 셀 크기와 충돌하여 텍스트가 잘리거나 넘침 |
| 증상 | "매우 크게(64px)" 설정 시 값이 셀 경계를 벗어남. 작은 셀에 큰 글자가 들어가면서 정보 가독성 저하 |
| 원인 | @container 반응형(text-xs @[200px]:text-3xl)은 셀 크기에 따라 자동 조절되는데, 절대 px가 이를 덮어씀 |
| 해결 | 글자 크기 커스텀 기능 전체 제거. @container 반응형 자동 크기만 유지. 정렬은 라벨만 좌/중/우 선택 |
| 날짜 | 2026-02-11 |
| 키워드 | 글자 크기, @container, 반응형, overflow, 대시보드 |
교훈
대시보드처럼 고밀도 정보를 표시하는 컴포넌트에서는 절대 크기 지정을 피하고, 컨테이너 크기에 따른 자동 반응형이 더 안정적이다. "정보를 한눈에 파악"이 목적인 컴포넌트에서는 정보가 잘리게 만드는 기능 자체가 목적에 반한다.
미사용 import (PopComponentType)
| 항목 | 내용 |
|---|---|
| 문제 | ComponentEditorPanel.tsx에서 COMPONENT_TYPE_LABELS 타입을 Record<PopComponentType, string> -> Record<string, string>으로 변경한 후, PopComponentType의 유일한 사용처가 사라졌으나 import가 남아있었음 |
| 해결 | import에서 PopComponentType 제거 |
| 날짜 | 2026-02-10 |
| 키워드 | 미사용 import, 타입 변경, PopComponentType |
교훈
타입을 변경하거나 제거할 때, 해당 타입이 import된 것이라면 변경 후 파일 내 다른 참조가 남아있는지 Grep으로 확인해야 한다.
새 문제-해결 추가 시 해당 카테고리 테이블에 행 추가