# 다음 세션 — ConfigPanel 리팩토링 + 잔여 마이그 todos

날짜: 2026-04-28
컨텍스트: 직전 세션에서 좌측 팔레트 11 컴포넌트 cp 톤 마이그 + Inv* 일괄 네이밍 통합 완료. 안전 복귀점: commit 이후 (git log 확인).

---

## 1순위: 리팩토링 본체 (Codex 권장)

### 1.1 useDbTables() hook 추출 ★ 가장 안전 — ✅ 완료 (2026-04-29)

**현재 중복 위치**:
- `lib/registry/components/search/InvSearchConfigPanel.tsx`
- `lib/registry/components/table/InvTableConfigPanel.tsx`
- `lib/registry/components/stats/InvStatsConfigPanel.tsx`
- `lib/registry/components/input/InvInputConfigPanel.tsx` (필요 시)

**중복 패턴**:
```tsx
const [allDbTables, setAllDbTables] = useState<any[]>([]);
useEffect(() => {
  let cancelled = false;
  (async () => {
    const { tableManagementApi } = await import("@/lib/api/tableManagement");
    const res = await tableManagementApi.getTableList();
    if (!cancelled && res.success && res.data) setAllDbTables(res.data);
  })();
  return () => { cancelled = true; };
}, []);

const tableOptions = useMemo(() => allDbTables.map(...), [allDbTables]);
```

**추출 위치**: `frontend/lib/registry/components/common/hooks/useDbTables.ts`

**hook 시그니처 (Codex 권장)**:
```ts
export function useDbTables(opts?: {
  fallback?: any[];           // search/table 의 props.tables fallback
  normalize?: (t: any) => { value: string; label: string };  // 기본 normalize
}): {
  tables: any[];
  options: { value: string; label: string }[];
  loading: boolean;
};
```

**주의 (Codex)**:
- `tableName/table_name`, `display_name/tableLabel/table_label` normalization 을 hook 안에 고정
- table 의 connected column loading / autoload 로직은 **hook 에 섞지 말 것** (별개)

**적용 결과 (2026-04-29)**:
- 신규 파일: `frontend/lib/registry/components/common/useDbTables.ts`
- 적용 패널: search / table / stats (3개)
- input 은 패턴 없어서 적용 안 함
- normalize 는 hook 안 default + `opts.normalize` override 가능 (Codex 권장 그대로)
- stats 만 value 우선순위 살짝 달랐으나 (`table_name || tableName`) functional 동일이라 default 통일
- TS 검증: 변경 4 파일 새 에러 0건

### 1.2 TableConnectSection 추출 (1.1 다음) — ✅ 완료 (2026-04-29)

**현재 중복**:
- search / table 의 "테이블 연결 + 자동 로드 버튼" 섹션
- stats 는 자동 로드 X (테이블 선택만)

**추출 시 주의**:
- search: `[자동 로드]` = 검색 필드 8개 자동 추출
- table: `[자동 로드]` = 컬럼 N개 자동 추출
- → autoload 콜백을 prop 으로 받음. label 도 prop.

**적용 결과 (2026-04-29)**:
- 신규 파일: `frontend/lib/registry/components/common/TableConnectSection.tsx`
- 두 컴포넌트 export:
  1. `TableConnectSection` — CPSection + CPRow "테이블" + CPSelect (children 슬롯)
  2. `AutoLoadButton` — 38줄 동일 inline 스타일 버튼 (label / onClick / iconSize prop)
- Codex 의 ★ 가이드 그대로: autoload 로직 (callback / 조건 / 컬럼 fetch) 호출처에 그대로 둠
- search 호출처는 `tableColumns.length > 0 && <AutoLoadButton ... />` + `!connectedTable && <Hint>` 직접 children 으로 주입
- table 호출처는 `<Hint>` (loading 상태) + `<AutoLoadButton ... />` 직접 children
- TS 검증: 변경 파일 새 에러 0건
- 변경 stat: 4 files, +81/-199 (호출처 순감 118줄, 신규 모듈 ~186줄, 총 net +68 — 가독성/유지보수 큰 이득)

### 1.3 DenseListRow / ExpandableRow 추출 (1.1, 1.2 다음) — ✅ helper-only 완료 (2026-04-29)

**현재 중복**:
- search 의 SearchFieldRow
- table 의 ColumnEditRow (펼침 있음)
- stats 의 ItemEditRow (펼침 있음)
- input 의 select options row (단순)

→ 패턴 안정화 후 추출. 너무 일찍 하면 over-abstraction.

**적용 결과 (2026-04-29) — Codex 가이드 따라 helper-only 좁게 추출**:

- 신규 파일: `frontend/lib/registry/components/common/row-helpers.tsx`
- 3 helper export:
  1. `RowNumberBadge` (n) — search/table/stats 의 #번호 (4 패널 중 3)
  2. `RowExpandChevron` (expanded, onToggle, size?) — table/stats 의 ▸/▾ 펼침 버튼 (2 패널)
  3. `RowDeleteBtn` (onClick, visible?, size?, children?, title?) — 4 패널 모두 사용
- ★ wrapper (DenseListRow / ExpandableRow) 는 **보류** — Codex 판단: row 별 expanded state 위치 / hover 표시 / row padding 회귀 위험 중간 이상
- silent visual change 1건: input 의 옵션 ×버튼 hover bg 추가됨 (이전 X) — Codex Y2 권고로 `hoverBg?: boolean` 옵션 추가 (기본 true), input 만 `hoverBg={false}` 로 원복
- Y1 ★ 경고 주석 반영: `RowDeleteBtn` 의 `visible` 기본값 true 위험 (호출처가 prop 생략 시 항상 노출) — 주석에 "dense list row 는 반드시 `visible={hover}` 명시" 명시
- 적용 패널: search (RowNumberBadge + RowDeleteBtn) / table + stats (3 helper 다) / input (RowDeleteBtn 만)
- TS 검증: 새 에러 0건
- 변경 stat 누적 (1.1+1.2+1.3): 5 files, +123 / -376 (호출처 -253 / 신규 3 모듈 +296 / net +43 — 가독성·유지보수 이득)

---

## 2순위: 잔여 V2* cp 마이그 (좌측 팔레트 외)

### 2.1 V2DateConfigPanel cp 마이그 (좌측 더보기 9)
- 위치: `frontend/components/v2/config-panels/V2DateConfigPanel.tsx`

### 2.2 V2BomTreeConfigPanel / V2BomItemEditorConfigPanel (좌측 BOM 트리/편집)

### 2.3 InvDataConfigPanel 의 list/table 분기
- 현재: V2ListConfigPanel / V2TableListConfigPanel(1497줄) 옛 디자인 그대로 위임
- 신규 배치는 v2-repeater 만 (InvRepeaterConfigPanel) — 옛 화면 호환용
- 우선순위 낮음 (사용자 신규 배치 X)

---

## 3순위: dead code 정리 (Codex 권장 방식)

`frontend/components/v2/config-panels/V2*ConfigPanel.tsx` 33개 중 실제 사용/미사용 식별:
- basename-driven `rg` (ripgrep) 스캔: `rg -l "V2XxxConfigPanel" frontend/`
- register/barrel 노이즈 구분 (config_panel: 매핑이 살아있는 것 = 사용 중)
- 파일명만으로 삭제 금지 — 호출처 확인 필수

---

## 4순위: IconPicker 공용 cp 톤 마이그 (사용자 지적) — ✅ 완료 (2026-04-29)

**문제**: button / stats 등 패널의 "아이콘 선택" 셀렉트가 다른 select 와 사이즈 다름 (Image #9 참고). IconPicker 컴포넌트 자체 디자인이 cp 톤 아님.

**위치**: `frontend/lib/registry/components/common/IconPicker.tsx`

**한 번 마이그하면 button / stats / 기타 IconPicker 사용 패널 일관성 회복.**

**적용 결과 (2026-04-29) — Codex Y4 권고 따라**:

- 위치: `frontend/lib/registry/components/common/IconPicker.tsx`
- 시그니처 / 동작 (외부 클릭 닫기 없음 등) **완전 동일**, 시각 톤만 마이그
- 트리거 버튼: 28px height, 12px font, cp-surface bg, cp-border + focus glow (primary 0.5/0.12), 6px radius
- 팝오버: cp-surface bg, cp-border, 6px radius, shadow
- 검색 input: cp-bg-subtle, cp-border, 4px radius, 11px font
- 그리드 버튼: cp hover (cp-surface-hover), primary 활성 tint (rgba(primary-rgb, 0.10) bg + primary text)
- shadcn 클래스 (`border-border bg-background text-xs` 등) 0건 → cp 변수 + inline style 전환

**영향 범위 검증**:
- 사용처 grep 결과 — IconPicker 본체는 button/stats 2 cp 패널만 사용
- 외부 7곳 (layout/admin/dash) 은 별개 `MenuIconPicker` 사용 → **영향 없음**

**검증**:
- TS 새 에러 0건
- diff stat: 1 file, +152 / -18 (cp inline style verbosity 로 size 증가, 시각 일관성 큰 이득)

**E3 후속 — popover Portal 도입 (2026-04-29)**:
- Codex 검증 시 ★ 발견: stats 의 list wrapper `overflow: hidden` (cp 표준 디자인) 안에서 IconPicker popover (absolute) 가 잘릴 위험
- search/table 도 동일 hidden 패턴 — cp 표준 깨면 일관성 깨짐
- 해결: IconPicker 의 popover 만 `React.createPortal(document.body)` + `position: fixed` + 좌표 (트리거 `getBoundingClientRect()`) 로 변경
- 부모 overflow 와 무관하게 항상 화면 위에 표시
- scroll/resize 발생 시 popover 자동 닫음 (좌표 안 따라감 — 단순)
- z-index 9999 로 최상위
- 다른 cp 패널 일관성 유지 (wrapper hidden 그대로)

**IconPicker 미완 후속 사항 (다음에 손볼 때 한꺼번에)** ★:

1. **viewport 하단 잘림 보정 (auto-flip)** — 트리거 아래 공간 부족 시 popover 를 트리거 위로 flip. 좌표 계산 시 `viewport.height - rect.bottom` 비교 후 결정.
2. **외부 클릭 닫기** — 현재 트리거 재클릭 / 그리드 선택 / 검색 후 선택 시만 닫힘. cp 표준 CPSelect 처럼 `mousedown` capture + Escape 키 처리 추가 (단 portal 안에서 처리 시 popover 자체 클릭은 닫지 않게 ref 비교).
3. **z-index 9999 표준화** — 향후 앱 모달/오버레이 (예: shadcn `Dialog`, `Sheet` 등) 와 충돌 가능. cp 표준 z-index 토큰 (`--cp-z-popover`) 정의 후 사용.
4. **viewport 우측 잘림 보정** — popover width 가 트리거 right 보다 클 때 좌측으로 shift. 현재 trigger.width 그대로 사용해 우측 잘림 가능성 낮지만, 동적 width 일 때 대비.
5. **키보드 네비** — ↑↓ 화살표 / Enter / Esc 등. 현재 마우스만 지원.
6. **카테고리 분류 / 더보기** — 현재 80개 cap. 검색 시에만 전체 매치. 카테고리 (액션/내비/IO 등) 추가 후보.

위 1~6 은 즉시 깨지는 결함은 아님. 다음에 IconPicker 본체 손볼 때 (예: button/stats 외 다른 패널 추가 또는 신규 기능 추가) 함께 처리.

---

## 안전 가이드

- **Codex 검토 필수**: 리팩토링 / 큰 rename 은 Codex 에게 검증받음 (silent breakage 방지)
- **commit 자주**: 각 1.x / 2.x 단계마다 commit (안전 복귀점)
- **dev 서버 재시작**: rename 후 ssh park@100.126.230.80 'docker restart invyone-frontend'
- **smoke test**: 좌측 팔레트 11 컴포넌트 + 옛 hidden 화면 (있다면) 1개씩 클릭 확인

---

## 진행 상황 요약 (2026-04-28 직전 세션 기준)

### 완료
- ✅ 좌측 팔레트 11 컴포넌트 cp 톤 마이그
- ✅ Inv* 일괄 네이밍 통합 (11 패널)
- ✅ 옛 호환 3 → InvLegacy* 분리
- ✅ getComponentConfigPanel.tsx stats key 중복 버그 수정
- ✅ ALIAS 충돌 수정 (옛 hidden 컴포넌트 → InvLegacy)
- ✅ input 통합 cp 톤 신규 (InvInputConfigPanel)
- ✅ **useDbTables hook 추출 (2026-04-29)**
- ✅ **TableConnectSection + AutoLoadButton 추출 (2026-04-29)**
- ✅ **Row helpers 추출 (RowNumberBadge / RowExpandChevron / RowDeleteBtn) — 2026-04-29**
- ✅ **IconPicker cp 톤 마이그 (2026-04-29)** — Codex Y4 권고

### 미완 (위 1~4 순위)
- ❌ V2DateConfigPanel / V2BomTree / V2BomItemEditor cp
- ❌ InvDataConfigPanel 의 list/table 분기 cp (옛 화면 호환용)
- ❌ V2* 33개 dead code 정리

---

## Codex 검토 (2026-04-29) 핵심 반영

| 질문 | 판정 | 시급 액션 |
|---|---|---|
| Q1 todos 우선순위 | 수정필요 | useDbTables 만 먼저 (✅ 완료) + dead-code 스캔 기준 보강 |
| Q2 InvRepeater 신규 | 수정필요 | `import` type 보류, resolver/writer + 보존 키 목록 확정 |
| Q3 InvData 통합 | 수정필요 | taxonomy 하위 축 추가 + round-trip DoD |
| Q4 Inv* 네이밍 | OK | 옵션 B 유지, legacy 사용량 0 검증 전 삭제 금지 |

### ★ silent breakage 위험 (Codex 경고)

1. **TableConnectSection 추출 시** — search/table 의 자동 로드 의미 다름 (search: 검색 필드 8개 / table: 컬럼 N개). UI 만 공통화 + autoload 는 prop callback
2. **DenseListRow 추출 시** — row 별 key/펼침/onChange shape 다름. 너무 빨리 공통화하면 UI 는 떠도 저장 config 틀어질 수 있음
3. **dead code 33개 식별** — `rg "V2XxxConfigPanel"` 만으로 부족
   - `getComponentConfigPanel.tsx` 의 `CONFIG_PANEL_MAP` + `CONFIG_PANEL_ALIAS` (alias 가 먼저 해석됨, 예: `"v2-table-list": "table"`)
   - `V2PropertiesPanel.tsx:215` hardcoded require
   - dynamic import map 확인 필수
4. **InvRepeater config 보존 키**
   - `source_detail_config` (table_name/foreign_key/parent_key 외 use_entity_join, column_mapping, additional_join_columns 도 보존)
   - `column_mappings[]`, `calculation_rules[]`, `entity_joins[]` (modal 전용처럼 보여도 보존)
   - `no_duplicate_pick` (현재 코드 L1335) 호환 체크 추가
5. **V2DateConfigPanel** — V2FieldConfigPanel 의 `input.date.*` 와 분리 결정 필요
6. **excludeFilter / linkedFilters[] / source_detail_config** — 통합 InvData 시 read/write round-trip 테스트 필수 (참조 테이블 변경 시 별도 컬럼 API 호출)

### 추가 권장사항

- **InvData 통합 패널 분리**: 3760줄 단일 파일 비추천. orchestrator / resolveAxis-applyAxis / read sections / write sections / API hooks / column editors 로 경계
- **InvRepeater resolver/writer 도입**: V2Field 의 resolveTriple/applyTriple 패턴 그대로 (InvRepeaterConfigPanel L124, L141 이미 같은 형태 사용 중)
- **InvRepeater type=import 보류**: 별도 reserved 또는 후속 컴포넌트 후보로
