# POP 개발 계획 --- ## 현재 상태 (2026-02-11) **Phase 0 공통 인프라 (usePopEvent + useDataSource) 구현 완료, ksh-v2-work 병합 + 원격 push 완료. Phase 2 pop-button 설계 진행 중.** --- ## 작업 순서 ``` [Phase 1~3] [Phase 5] [정의서] [Phase 0~6] v4 Flexbox → v5 CSS Grid → 컴포넌트 설계 → 실제 구현 완료 완료 (v5.2) 완료 (v8.0) 다음 ``` --- ## 완료된 Phase ### Phase 1~3: v4 Flexbox 시스템 (완료, 레거시 삭제됨) v4 Flexbox 기반 시스템은 v5 CSS Grid로 완전히 대체되었습니다. v4 관련 파일은 모두 삭제되었습니다. - [x] v4 기본 구조, 렌더러, 디자이너 통합 - [x] Undo/Redo, 드래그 리사이즈, Flexbox 가로 배치 - [x] 비율 스케일링 시스템 - [x] 오버라이드 기능 (모드별 배치 고정) - [x] 컴포넌트 표시/숨김, 줄바꿈 ### Phase 5: v5 CSS Grid 시스템 (완료) #### Phase 5.1: 타입 정의 (완료) - [x] `PopLayoutDataV5` 인터페이스 - [x] `PopGridConfig`, `PopGridPosition` 타입 - [x] `GridMode`, `GRID_BREAKPOINTS` 상수 - [x] `createEmptyPopLayoutV5()`, `isV5Layout()`, `detectGridMode()` #### Phase 5.2: 그리드 렌더러 (완료) - [x] `PopRenderer.tsx` - CSS Grid 기반 렌더링 - [x] 격자 셀 렌더링 (CSS Grid 동일 좌표계) - [x] 위치 변환 (12칸 -> 4/6/8칸) #### Phase 5.3: 디자이너 UI (완료) - [x] `PopCanvas.tsx` - 그리드 캔버스 + 행/열 라벨 - [x] 드래그 스냅 (칸에 맞춤) - [x] `ComponentEditorPanel.tsx` - 위치 편집 #### Phase 5.4: 반응형 자동화 (완료) - [x] 자동 변환 알고리즘 (12칸 -> 4칸) - [x] 겹침 감지 및 재배치 - [x] 모드별 오버라이드 저장 #### v5.1 추가 기능 (완료) - [x] 자동 줄바꿈 (col > maxCol -> 맨 아래 배치) - [x] "검토 필요" 알림 시스템 - [x] Gap 프리셋 (좁게/보통/넓게) - [x] 숨김 기능 (모드별) #### v5.2 브레이크포인트 재설계 + 세로 자동 확장 (완료) - [x] 기기 기반 브레이크포인트 (479/767/1023px) - [x] 세로 자동 확장 (dynamicCanvasHeight) - [x] 뷰어 반응형 일관성 (detectGridMode 사용) - [x] VIEWPORT_PRESETS에서 height 제거 ### 컴포넌트 설계 (완료) - [x] 9개 컴포넌트 정의 (POPUPDATE_2.md v8.0) - [x] POP 헌법 9조 작성 - [x] 공통 인프라 설계 (DataSourceConfig, ColumnBinding, JoinConfig, useDataSource, usePopEvent, PopActionConfig) - [x] 모달 화면 설계 방식 확정 (인라인 + 외부 참조) - [x] 기존 시스템 호환성 검증 (DB/백엔드/프론트 변경 불필요 확인) --- ## 다음 작업 ### Phase 0: 공통 인프라 모든 데이터 연동 컴포넌트가 공유하는 기반 시스템: - [x] ColumnBinding 타입 정의 (read/write/readwrite/hidden) -- types.ts에 추가 완료 - [x] JoinConfig 타입 정의 (테이블 조인) -- types.ts에 추가 완료 - [x] DataSourceConfig 타입 정의 (데이터 소스 설정) -- types.ts에 추가 완료 - [x] PopActionConfig 타입 정의 (액션 설정) -- types.ts에 추가 완료 - [x] usePopEvent 훅 구현 (이벤트 버스, 데이터 전달, 화면 단위 격리) -- 완료 - [x] useDataSource 훅 구현 (CRUD 포함, 기존 dataApi 활용) -- 완료 ### Phase 1: pop-dashboard -- 완료 > **2026-02-10**: 17단계 코딩 + 검수 + 팔레트 등록 완료 - [x] PopDashboardConfig, DashboardItem 타입 정의 - [x] 멀티 아이템 컨테이너 구현 (여러 아이템 묶음) - [x] 4개 서브타입: kpi-card, chart, gauge, stat-card - [x] 4개 표시 모드: arrows, auto-slide, grid, scroll - [x] 계산식(formula) 지원: "생산량/총재고량" 같은 복합 표현 - [x] 설정 패널: 드롭다운 기반 쉬운 집계 설정 (SQL 불필요) - [x] PopComponentRegistry 등록 + 디자이너 팔레트 등록 - [ ] 이벤트: filter_changed 수신, kpi_clicked 발행 -- usePopEvent 완성 후 - [ ] 기존 `components/pop/dashboard/` 폴더 폐기 (모든 기능 대체 확인 후) ### Phase 2: pop-button, pop-icon - [ ] pop-button: 저장/삭제/API 호출 액션 - [ ] pop-icon: 화면 이동/URL/새로고침 ### Phase 3: pop-table (테이블형 우선) - [ ] table-list 서브타입: 행/열 장부형 - [ ] ColumnBinding 기반 컬럼별 read/write - [ ] card-list 서브타입: 카드 템플릿 (후순위) ### Phase 4: pop-search, pop-field, pop-lookup - [ ] pop-search: 필터 조건 입력 (text/date/select/combo) - [ ] pop-field: 저장용 입력 (text/number/date/select/numpad) - [ ] pop-lookup: 모달 값 선택 (인라인 + 외부 참조) ### Phase 5: 고도화 - [ ] pop-table 카드 템플릿 디자이너 ### Phase 6: pop-system - [ ] 프로필, 테마, 대시보드 보이기/숨기기 통합 ### 후속 작업 - [ ] 워크플로우 연동 (버튼 액션, 화면 전환) - [ ] 실기기 테스트 (아이폰 SE, iPad Mini 등) **참고 문서**: [POPUPDATE_2.md](../POPUPDATE_2.md) (컴포넌트 정의서 v8.0) --- ## 현재 구현 계획 > **용도**: 이 섹션은 "지금 바로 실행할 구체적 계획"입니다. > 새 세션에서 이 섹션만 읽으면 코딩을 시작할 수 있어야 합니다. > 완료되면 다음 기능의 계획으로 **교체**합니다. ### 대상: Phase 0 공통 인프라 (usePopEvent + useDataSource 훅) #### 배경 (2026-02-11) 모든 데이터 연동 POP 컴포넌트(pop-button, pop-table, pop-search 등)가 공유하는 2개 핵심 훅을 구현한다. - **usePopEvent**: 같은 화면(screenId) 안에서 컴포넌트 간 이벤트 통신 (publish/subscribe) - **useDataSource**: DB 테이블 CRUD 통합 (조회/생성/수정/삭제) **핵심 원칙**: 새로 만드는 것이 아니라, 기존 코드를 공통화하는 작업이다. - `useDataSource`는 대시보드의 `dataFetcher.ts` 조회 로직 + 기존 `dataApi` CRUD를 훅으로 래핑 - `usePopEvent`는 신규 구현 (Map 기반 이벤트 버스) - 대시보드는 **이번에 교체하지 않는다** (훅 안정화 후 별도 교체) #### 결정사항 (2026-02-11) | 항목 | 결정 | 이유 | |------|------|------| | usePopEvent 범위 | 같은 screenId 안에서만 통신 | 화면 간 의존성 방지 | | usePopEvent 모달 | 같은 screenId면 모달 안 컴포넌트도 통신 가능 | 모달은 별도 화면이 아님 | | useDataSource 조회 분기 | 집계/조인이면 SQL 빌더 + executeQuery, 단순이면 dataApi.getTableData | 대시보드 dataFetcher.ts와 동일 전략 | | useDataSource CRUD | dataApi.createRecord/updateRecord/deleteRecord 래핑 | 백엔드 API 이미 완성됨 | | 대시보드 교체 시점 | 이번에 하지 않음, 훅 안정화 후 별도 작업 | 안정성 우선 | | SQL 빌더 위치 | dataFetcher.ts에서 추출하여 별도 유틸로 분리 | 훅과 대시보드 모두 사용 | | 이벤트 버스 저장소 | 전역 Map (screenId -> EventEmitter) | React 외부에서도 접근 가능, GC 관리 용이 | --- #### 구현 순서 (의존성 기반) | 순서 | 파일 | 작업 | 의존성 | 상태 | |------|------|------|--------|------| | 1 | `hooks/pop/usePopEvent.ts` | 이벤트 버스 훅 (신규) | 없음 | **완료** | | 2 | `hooks/pop/popSqlBuilder.ts` | SQL 빌더 유틸 분리 (dataFetcher.ts에서 추출) | 없음 | **완료** | | 3 | `hooks/pop/useDataSource.ts` | 데이터 CRUD 훅 (신규) | 2 | **완료** | | 4 | `hooks/pop/index.ts` | 배럴 파일 (re-export) | 1, 3 | **완료** | --- #### STEP 1: `usePopEvent.ts` (신규 생성) **파일**: `frontend/hooks/pop/usePopEvent.ts` **역할**: 같은 화면(screenId) 안에서 컴포넌트 간 이벤트 통신 **핵심 구조**: ``` 전역 저장소 (React 외부) screenBuses: Map>> │ └── screenId: "S001" ├── "supplier-selected" → [콜백A, 콜백B] ├── "data-saved" → [콜백C] └── sharedData: Map sharedDataStore: Map> │ └── screenId: "S001" ├── "selectedSupplier" → { id: "SUP-001", name: "삼성" } └── "inputQuantity" → 50 ``` **외부 API (훅이 반환하는 것)**: ```typescript function usePopEvent(screenId: string) { return { publish, // (eventName, payload) => void subscribe, // (eventName, callback) => unsubscribe 함수 getSharedData, // (key) => unknown setSharedData, // (key, value) => void }; } ``` **상세 구현 명세**: 1. **전역 Map 저장소** (모듈 스코프, React 외부) - `screenBuses: Map>>` - 이벤트 리스너 - `sharedDataStore: Map>` - 공유 데이터 2. **`publish(eventName, payload)`** - 해당 screenId의 eventName에 등록된 모든 콜백을 순회하며 payload 전달 - 등록된 리스너가 없으면 아무 일도 안 함 (에러 아님) 3. **`subscribe(eventName, callback)`** - 해당 screenId의 eventName에 콜백 등록 - **반환값**: unsubscribe 함수 - **useEffect 내부에서 호출되어야 함** (cleanup으로 unsubscribe) 4. **`getSharedData(key)`** / **`setSharedData(key, value)`** - screenId별 격리된 key-value 저장소 - publish/subscribe는 "이벤트"(일회성), sharedData는 "상태"(지속) - 용도: 버튼이 저장할 때 다른 컴포넌트들의 현재 값을 수집 5. **`cleanupScreen(screenId)`** (내부 유틸) - 화면 언마운트 시 해당 screenId의 모든 리스너 + sharedData 정리 - 메모리 누수 방지 **사용 예시**: ```typescript // 거래처 선택 버튼 const { publish, setSharedData } = usePopEvent("S001"); const onSelect = (supplier) => { setSharedData("selectedSupplier", supplier); // 상태 저장 publish("supplier-selected", { supplierId: supplier.id }); // 이벤트 발행 }; // 발주 테이블 (다른 컴포넌트) const { subscribe } = usePopEvent("S001"); useEffect(() => { const unsub = subscribe("supplier-selected", (payload) => { refetch({ filters: { supplier_id: payload.supplierId } }); }); return unsub; // cleanup }, []); // 저장 버튼 (다른 컴포넌트) const { getSharedData } = usePopEvent("S001"); const handleSave = () => { const supplier = getSharedData("selectedSupplier"); // 다른 컴포넌트가 저장한 값 수집 const quantity = getSharedData("inputQuantity"); save({ supplier_id: supplier.id, quantity }); }; ``` --- #### STEP 2: `popSqlBuilder.ts` (신규 생성 - dataFetcher.ts에서 추출) **파일**: `frontend/hooks/pop/popSqlBuilder.ts` **역할**: DataSourceConfig를 SQL 문자열로 변환하는 순수 유틸리티 **기존 dataFetcher.ts에서 그대로 추출할 함수 5개** (로직 변경 없음): | 함수 | 원본 위치 (dataFetcher.ts) | 역할 | |------|--------------------------|------| | `escapeSQL(value)` | 라인 41~48 | SQL 값 이스케이프 | | `sanitizeIdentifier(name)` | 라인 124~127 | 테이블/컬럼명 위험 문자 제거 | | `validateDataSourceConfig(config)` | 라인 59~88 | 설정 완료 여부 검증 | | `buildWhereClause(filters)` | 라인 93~118 | 필터 -> WHERE 절 변환 | | `buildAggregationSQL(config)` | 라인 137~215 | DataSourceConfig -> SELECT SQL 변환 | **export 대상**: `validateDataSourceConfig`, `buildAggregationSQL` (나머지는 내부 함수) **주의**: dataFetcher.ts는 **수정하지 않는다**. 대시보드가 계속 사용 중이므로, 복사만 한다. 대시보드 교체 시점에 dataFetcher.ts에서 이 파일을 import하도록 변경할 예정. --- #### STEP 3: `useDataSource.ts` (신규 생성) **파일**: `frontend/hooks/pop/useDataSource.ts` **역할**: DataSourceConfig 기반 DB 테이블 CRUD 통합 훅 **내부 의존성**: - `popSqlBuilder.ts` - SQL 빌더 (STEP 2) - `@/lib/api/data` - dataApi (기존, 조회/생성/수정/삭제) - `@/lib/api/dashboard` - dashboardApi (기존, SQL 직접 실행) - `@/lib/api/client` - apiClient (기존, axios 기반) **외부 API (훅이 반환하는 것)**: ```typescript function useDataSource(config: DataSourceConfig) { return { // 상태 data: { rows: [], value: 0, total: 0 }, loading: boolean, error: string | null, // 조회 refetch: (overrideFilters?) => Promise, // 쓰기 save: (record) => Promise, update: (id, record) => Promise, remove: (id) => Promise, }; } ``` **MutationResult 타입** (신규): ```typescript interface MutationResult { success: boolean; data?: any; error?: string; } ``` **조회 분기 로직 (핵심)**: ``` config에 aggregation 또는 joins가 있는가? ├── YES → buildAggregationSQL(config) → apiClient.post("/dashboards/execute-query") │ (대시보드와 동일한 경로, SQL 직접 실행) │ 실패 시 → dashboardApi.executeQuery() 폴백 │ └── NO → dataApi.getTableData(tableName, { page, size, filters, sortBy, sortOrder }) (단순 테이블 조회) ``` **CRUD 메서드 구현**: | 메서드 | 내부 호출 | 비고 | |--------|----------|------| | `save(record)` | `dataApi.createRecord(config.tableName, record)` | company_code 자동 추가는 백엔드가 처리 | | `update(id, record)` | `dataApi.updateRecord(config.tableName, id, record)` | | | `remove(id)` | `dataApi.deleteRecord(config.tableName, id)` | 복합키 객체도 지원 | **자동 새로고침**: ```typescript useEffect(() => { if (config.tableName) refetch(); if (config.refreshInterval && config.refreshInterval > 0) { const sec = Math.max(5, config.refreshInterval); // 최소 5초 const timer = setInterval(refetch, sec * 1000); return () => clearInterval(timer); } }, [config.tableName, config.refreshInterval]); ``` **refetch 오버라이드 필터**: ```typescript // 기본 조회 refetch(); // 필터 추가하여 조회 (usePopEvent와 연동 시) refetch({ filters: { supplier_id: "SUP-001" } }); ``` 내부적으로 `overrideFilters`가 있으면 `config.filters`에 병합하여 조회한다. **사용 예시**: ```typescript // 대시보드 스타일 (집계) const { data, loading } = useDataSource({ tableName: "sales_order", aggregation: { type: "sum", column: "amount", groupBy: ["category"] }, refreshInterval: 30, }); // data.rows → [{ category: "A", value: 1500 }, ...] // data.value → 첫 번째 행의 value // 테이블 스타일 (목록) const { data, refetch } = useDataSource({ tableName: "purchase_order", sort: [{ column: "created_at", direction: "desc" }], limit: 20, }); // data.rows → [{ id: 1, item_name: "볼트", ... }, ...] // data.total → 전체 행 수 // 버튼 스타일 (저장만) const { save, remove, loading } = useDataSource({ tableName: "inbound_record", }); const result = await save({ supplier_id: "SUP-001", quantity: 50 }); // result.success → true/false ``` --- #### STEP 4: `index.ts` (신규 생성 - 배럴 파일) **파일**: `frontend/hooks/pop/index.ts` ```typescript export { usePopEvent, cleanupScreen } from "./usePopEvent"; export { useDataSource } from "./useDataSource"; export type { MutationResult } from "./useDataSource"; export { buildAggregationSQL, validateDataSourceConfig } from "./popSqlBuilder"; ``` 외부에서 사용할 때: `import { usePopEvent, useDataSource } from "@/hooks/pop";` --- #### 사전 충돌 검사 결과 (2026-02-11) | 이름 | 유형 | 검색 범위 | 검색 결과 | 판정 | |------|------|-----------|-----------|------| | `usePopEvent` | 훅 이름 | frontend 전체 | **주석 2건** (PopDashboardComponent.tsx 라인 10, `@INFRA-EXTRACT` 교체 예정 주석) | **충돌 없음** (실제 코드 아님) | | `useDataSource` | 훅 이름 | frontend 전체 | **주석 4건** (dataFetcher.ts 라인 4,227,355 + PopDashboardComponent.tsx 라인 9, 모두 `@INFRA-EXTRACT` 주석) | **충돌 없음** (실제 코드 아님) | | `PopEventBus` | 클래스명 | frontend 전체 | **1건** (PopDashboardComponent.tsx 라인 10, `@INFRA-EXTRACT` 주석 내) | **충돌 없음** (주석) | | `MutationResult` | 타입명 | frontend 전체 | **0건** | **충돌 없음** | | `popSqlBuilder` | 파일명 | frontend 전체 | **0건** | **충돌 없음** | | `cleanupScreen` | 함수명 | frontend 전체 | **0건** | **충돌 없음** | | `screenBuses` | 변수명 | frontend 전체 | **0건** | **충돌 없음** | | `sharedDataStore` | 변수명 | frontend 전체 | **0건** | **충돌 없음** | | `buildAggregationSQL` | 함수명 | frontend 전체 | **2건** (dataFetcher.ts 정의 + PopDashboardComponent에서 import) | **충돌 주의**: 동일 이름을 popSqlBuilder.ts에서 재정의. 대시보드는 여전히 dataFetcher.ts 것을 사용하므로 런타임 충돌 없음. 향후 대시보드 교체 시 import 경로만 변경. | | `validateDataSourceConfig` | 함수명 | frontend 전체 | **1건** (dataFetcher.ts 정의) | 위와 동일 | | `escapeSQL` | 함수명 | frontend 전체 | **1건** (dataFetcher.ts 내부 함수) | **충돌 없음** (export 안 됨) | | `sanitizeIdentifier` | 함수명 | frontend 전체 | **1건** (dataFetcher.ts 내부 함수) | **충돌 없음** (export 안 됨) | | `AggregatedResult` | 타입명 | frontend 전체 | **1건** (dataFetcher.ts 정의) | **충돌 주의**: useDataSource 내부에서 동일 타입 사용. types.ts 정의를 공유하므로 별도 재정의 불필요. | #### 정의-사용 매핑 | 정의 | 정의 위치 | 사용 위치 | |------|-----------|-----------| | `usePopEvent` | `hooks/pop/usePopEvent.ts` (신규) | pop-button (Phase 2), pop-table (Phase 3), pop-search (Phase 4) 등 | | `useDataSource` | `hooks/pop/useDataSource.ts` (신규) | pop-button (Phase 2), pop-table (Phase 3), pop-dashboard (향후 교체) 등 | | `MutationResult` | `hooks/pop/useDataSource.ts` (신규) | useDataSource 반환 타입으로 사용 | | `buildAggregationSQL` | `hooks/pop/popSqlBuilder.ts` (신규) | useDataSource 내부에서 호출 | | `validateDataSourceConfig` | `hooks/pop/popSqlBuilder.ts` (신규) | useDataSource 내부에서 호출 | | `cleanupScreen` | `hooks/pop/usePopEvent.ts` (신규) | 화면 언마운트 시 호출 (PopRenderer 또는 뷰어에서) | | `DataSourceConfig` | `lib/registry/pop-components/types.ts` (기존) | useDataSource 파라미터 타입 | | `DataSourceFilter` | `lib/registry/pop-components/types.ts` (기존) | popSqlBuilder 내부 | | `dataApi` | `lib/api/data.ts` (기존) | useDataSource 내부에서 CRUD 호출 | | `dashboardApi` | `lib/api/dashboard.ts` (기존) | useDataSource 내부에서 SQL 실행 폴백 | | `apiClient` | `lib/api/client.ts` (기존) | useDataSource 내부에서 SQL 실행 1차 | **누락 검사**: 모든 신규 정의에 사용처 있음. 모든 사용처에 정의 존재. 단, `cleanupScreen`의 호출 시점은 Phase 2 이후 뷰어 통합 시 결정 (이번에는 export만 해둠). --- #### 함정 경고 | 번호 | 위험 | 설명 | 해결 방안 | |------|------|------|-----------| | W1 | **subscribe를 useEffect 밖에서 호출하면 메모리 누수** | subscribe는 콜백을 등록하므로, 컴포넌트 언마운트 시 해제해야 함 | subscribe의 반환값(unsubscribe)을 useEffect cleanup에서 호출. JSDoc에 사용 패턴 명시. | | W2 | **DataSourceConfig의 import 경로** | `DataSourceConfig`는 `lib/registry/pop-components/types.ts`에 정의됨. hooks 디렉토리에서 import 시 경로가 김 | `@/lib/registry/pop-components/types`로 import. 별도 re-export 하지 않음 (타입 중복 방지). | | W3 | **buildAggregationSQL 동일 이름 2곳** | dataFetcher.ts와 popSqlBuilder.ts에 같은 이름의 함수가 존재 | 의도적 복사. 대시보드는 dataFetcher.ts, 새 컴포넌트는 popSqlBuilder.ts 사용. 향후 대시보드 교체 시 dataFetcher.ts를 popSqlBuilder.ts import로 변경. | | W4 | **apiClient.post와 dashboardApi.executeQuery 이중 경로** | 대시보드 dataFetcher.ts에서 apiClient 우선 + dashboardApi 폴백 패턴을 그대로 복사함 | 동일 패턴 유지 (안정성 검증 완료). 향후 하나로 통합 가능. | | W5 | **refetch overrideFilters와 config.filters 병합 순서** | overrideFilters가 config.filters를 완전 대체하는지, 추가하는지 모호 | **추가(append) 방식**: config.filters + overrideFilters를 합침. overrideFilters에 같은 column이 있으면 덮어씀. 이 동작을 JSDoc에 명시. | | W6 | **SSR 환경에서 전역 Map** | Next.js SSR에서 전역 Map이 서버/클라이언트 간 공유될 수 있음 | `typeof window !== "undefined"` 가드. 이벤트 버스는 클라이언트 전용. | | W7 | **hooks/pop/ 디렉토리 신규** | `frontend/hooks/pop/` 디렉토리가 존재하지 않음 | STEP 1에서 파일 생성 시 디렉토리 자동 생성됨. 수동으로 mkdir 불필요. | --- #### 작업 완료 후 확인 체크리스트 ##### 코드 레벨 - [ ] `usePopEvent` - publish/subscribe 기본 동작 (같은 screenId) - [ ] `usePopEvent` - 다른 screenId 간 격리 확인 - [ ] `usePopEvent` - subscribe cleanup (메모리 누수 없음) - [ ] `usePopEvent` - sharedData set/get - [ ] `useDataSource` - 단순 조회 (aggregation 없음 → dataApi.getTableData) - [ ] `useDataSource` - 집계 조회 (aggregation 있음 → SQL 빌더 → executeQuery) - [ ] `useDataSource` - save/update/remove - [ ] `useDataSource` - loading/error 상태 관리 - [ ] `useDataSource` - refreshInterval 자동 새로고침 - [ ] `popSqlBuilder` - buildAggregationSQL이 dataFetcher.ts와 동일 결과 생성 - [ ] TypeScript 컴파일 에러 0건 - [ ] 린트 에러 0건 - [ ] 기존 대시보드 동작에 영향 없음 (dataFetcher.ts 미수정) ##### 구조 레벨 - [ ] `frontend/hooks/pop/` 디렉토리 생성됨 - [ ] index.ts 배럴 파일에서 모든 public API export 됨 - [ ] `@/hooks/pop`으로 import 가능 --- #### 이전 완료된 계획 (보관) **대시보드 스타일 정리 (2026-02-11, 완료)**: 글자 크기 커스텀 제거, 라벨 정렬만 유지, stale closure 수정, .next 캐시 해결. 상세: `popdocs/sessions/2026-02-11.md` **브라우저 확인 체크리스트 (대기)**: - [ ] 라벨 정렬, 페이지 미리보기, 차트 디자인, 게이지/통계카드 동작 확인 --- ## 브레이크포인트 (v5.2 현재) | 모드 | 화면 너비 | 칸 수 | 대상 기기 | |------|----------|-------|----------| | mobile_portrait | ~479px | 4칸 | 아이폰 SE ~ 갤럭시 S | | mobile_landscape | 480~767px | 6칸 | 스마트폰 가로 | | tablet_portrait | 768~1023px | 8칸 | 8~10인치 태블릿 세로 | | tablet_landscape | 1024px~ | 12칸 | 10~14인치 태블릿 가로 | --- ## 관련 문서 | 문서 | 내용 | |------|------| | [STATUS.md](./STATUS.md) | 현재 진행 상태 | | [SPEC.md](./SPEC.md) | 기술 스펙 | | [ARCHITECTURE.md](./ARCHITECTURE.md) | 코드 구조 | | [POPUPDATE_2.md](../POPUPDATE_2.md) | 컴포넌트 정의서 v8.0 (최신) | | [components-spec.md](./components-spec.md) | 컴포넌트 상세 설계 (v4 기준, 갱신 필요) | | [decisions/005](./decisions/005-breakpoint-redesign.md) | 브레이크포인트 재설계 ADR | --- *최종 업데이트: 2026-02-11 (Phase 0 공통 인프라 완료, Phase 2 pop-button 설계 시작)*