# 2026-05-19 Canonical Data View Cleanup — Follow-up Report

이전 보고서: `notes/gbpark/2026-05-18-canonical-data-view-cleanup-report.md`
Goal 원본: `notes/gbpark/2026-05-18-canonical-data-view-goal.md`

작업 범위: 이전 보고서에서 "보존 with reason" 으로 분류된 항목 중 hard blocker 또는
explicit domain 이 아닌 모든 항목에 대해 실제 canonical 어댑터 구현 / 옛 경로 삭제 /
schema 정리. "classified" 만으로는 done 카운트 X.

---

## 1. Summary

총 **9개** 폴더 + 2개 V2 ConfigPanel 파일 삭제, 12개 옛 ConfigPanel 직접 import 제거,
9개 옛 Renderer 자동등록 제거, 4개 V2 schema/default 제거. 모든 acceptance check
통과 + frontend 변경 폴더에 새 import 오류 0건.

> ★ 2026-05-19 후속 수정: Codex 검증에서 canonical cleanup 범위를 넘어
> `control/dash` 데모/대시보드 변경이 추가됐음이 확인됨. 이 범위 밖 변경 9개 파일
> (tracked) + 1개 untracked 파일을 HEAD 로 되돌렸음. 자세한 내용은 §9 참조.

---

## 2. 실제 삭제 (Deleted paths)

### 2.1 Renderer 자동등록 (registry/components/index.ts 에서 제거) — 10개

`alias 라우팅으로 충분한 옛 Renderer auto-register` 분류 후 import side-effect 제거:

| 제거된 import | canonical alias |
|---|---|
| `./aggregation-widget/AggregationWidgetRenderer` | → `stats` |
| `./v2-aggregation-widget/AggregationWidgetRenderer` | → `stats` |
| `./v2-status-count/StatusCountRenderer` | → `stats` |
| `./tabs/tabs-component` | → `container` |
| `./v2-tabs-widget/tabs-component` | → `container` |
| `./section-card/SectionCardRenderer` | → `container` |
| `./v2-section-card/SectionCardRenderer` | → `container` |
| `./section-paper/SectionPaperRenderer` | → `container` |
| `./v2-section-paper/SectionPaperRenderer` | → `container` |

### 2.2 ConfigPanel 직접 import (getComponentConfigPanel.tsx 에서 제거) — 12개

`alias 우선 라우팅` 분류 후 CONFIG_PANEL_MAP 직접 import 제거:

- `section-card` / `v2-section-card` / `section-paper` / `v2-section-paper`
- `table-list` / `v2-table-list`
- `tabs-widget` / `v2-tabs-widget` / `tabs` / `v2-tabs`
- `aggregation-widget` / `v2-aggregation-widget`

`LEGACY_PANELS` 분기에서도 props 시그니처 불일치 위험으로 `stats` / `chart` /
`chart-basic` 제거 (canonical 은 config/onChange, LEGACY 는 component/onUpdateProperty).

### 2.3 V2 Schema/Default registry (componentConfig.ts 에서 제거) — 4개

- `v2-aggregation-widget` schema + default
- `v2-section-card` schema + default
- `v2-section-paper` schema + default
- `v2-tabs-widget` schema + default

### 2.4 폴더 자체 삭제 — 9개

| 폴더 | 외부 import 검증 |
|---|---|
| `lib/registry/components/aggregation-widget/` | 0건 |
| `lib/registry/components/tabs/` | 0건 |
| `lib/registry/components/v2-tabs-widget/` | 0건 |
| `lib/registry/components/section-card/` | 0건 |
| `lib/registry/components/v2-section-card/` | 0건 |
| `lib/registry/components/section-paper/` | 0건 |
| `lib/registry/components/v2-section-paper/` | 0건 |
| `lib/registry/components/v2-aggregation-widget/` | V2AggregationWidgetConfigPanel 만 (같이 삭제) |
| `lib/registry/components/v2-status-count/` | V2StatusCountConfigPanel 만 (같이 삭제) |

### 2.5 V2*ConfigPanel.tsx (components/v2/config-panels/) — 2개

- `V2AggregationWidgetConfigPanel.tsx` (1085 lines)
- `V2StatusCountConfigPanel.tsx` (679 lines)

cyclical import 만 있었고 외부 사용처 0 — 안전 삭제.

### 2.6 그 외 cleanup

- `withContainerQuery.css` 의 `v2-aggregation-widget` @container 제거
- `types/v2-components.ts` 의 `table-list` / `tabs-widget` / `section-card` /
  `section-paper` 매핑 제거
- `getComponentConfigPanel.CONFIG_PANEL_ALIAS` 에 `tabs-widget` / `tabs` / `v2-tabs`
  추가 (CONFIG_PANEL_MAP 옛 import 제거 보완), `accordion-basic` 제외
  (canonical container.accordion skeleton 부족)

---

## 3. 구현된 canonical replacement

| 옛 경로 | canonical replacement |
|---|---|
| `v2-aggregation-widget`, `aggregation-widget`, `v2-status-count` (palette) | canonical `stats` (palette BASIC_IDS) |
| `v2-tabs-widget`, `tabs-widget`, `tabs` (palette) | canonical `container` (containerType=tabs) |
| `v2-section-card`, `section-card` (palette) | canonical `container` (containerType=section, sectionVariant=card) |
| `v2-section-paper`, `section-paper` (palette) | canonical `container` (containerType=section, sectionVariant=paper) |
| 옛 stats config 직접 import (Studio) | InvStatsConfigPanel via alias |
| 옛 tabs / section config 직접 import (Studio) | InvContainerConfigPanel via alias |
| `RealtimePreviewDynamic.fillParentTypes` | canonical `table` / `container` / `grouped-table` / `card-list` 인식 |
| `InvyoneStudio` 9곳 tabs 분기 | canonical container (containerType=tabs) 인식 추가 |
| `ButtonPrimaryComponent` data provider 자동탐색 | canonical `table` 인식 |
| `ActionTab` / `DataTab` / `InvLegacyButtonConfigPanel` data transfer 후보 | canonical `table` 인식 (10곳) |
| `ScreenNode` / `InteractiveScreenViewer` / `TabsWidget` | canonical `table` / `container` 인식 |

---

## 4. Hard blockers (남은 보존 with reason)

이 항목들은 canonical 구현이 미완 또는 외부 의존 때문에 폴더 / Renderer / ConfigPanel
보존이 필수. 분류가 아니라 hard blocker.

| 보존 path | Hard blocker reason | 해결 조건 |
|---|---|---|
| `lib/registry/components/table-list/` | `FlowWidget` 이 `SingleTableWithSticky` + `ColumnConfig` 를 직접 import (Goal §8.1) | FlowWidget 을 canonical table 기반으로 마이그레이션, 또는 SingleTableWithSticky 를 shared 위치로 추출 |
| `lib/registry/components/v2-table-list/` | 옛 저장 layout 매우 많음 + canonical TableConfig 호환 검증 미완. table-list 보존과 함께 V2 도 보존 | DB 상 사용 화면 0건 + column ordering / export / context menu / card mode parity 검증 후 |
| `lib/registry/components/split-panel-layout/` `v2-split-panel-layout/` `split-panel-layout2/` | leftPanel/rightPanel master-detail UX, drag/drop, resize, selection sync, nested child 모두 canonical table displayMode='split' 에 없음. `SplitPanelContext` provider 가 app/(main)/screens/[screenId]/page.tsx, EditModal, ScreenModal 등 다수 사용 | canonical table 의 split 모드 master-detail UX 전부 구현 후 |
| `lib/registry/components/accordion-basic/` | canonical container.containerType=accordion skeleton 만 있음 | canonical container accordion 모드 완성 후 |
| `lib/registry/components/repeat-container/` `v2-repeat-container/` | canonical container.containerType=repeater 데이터 lookup / 선택 / append / 인라인 add 인프라 부족 | canonical container repeater 완성 후 |
| `lib/registry/components/v2-repeater/` | `basicV2Components` palette item (별도 데이터 조회/선택 컴포넌트) | canonical container repeater + canonical table multi-select 가 동일 UX 구현 후 |
| `lib/registry/components/conditional-container/` | canonical container.containerType=conditional skeleton 만 있음 | canonical container conditional 모드 완성 후 |
| `lib/registry/components/modal-repeater-table/` `simple-repeater-table/` `tax-invoice-list/` | business / domain 특화. canonical table parity 부족 | 도메인별 별도 마이그레이션 |
| `multilangLabelExtractor.ts` / `MultilangSettingsModal.tsx` (aggregation-widget i18n) | 옛 멀티랭 데이터 추출 영향 범위 미확정 | 멀티랭 마이그레이션 검증 후 |
| `layoutV2Converter.ts` | `InvyoneStudio` / `EditModal` / `ScreenModal` / `app/(main)/screens/[screenId]/page.tsx` 핵심 layer↔V2 변환 | 솔루션 전체 V2 단일 포맷 표준화 후 |
| `app/test-card-responsive/page.tsx` | ResizeObserver 패턴 테스트 페이지 | 별도 테스트 페이지 cleanup 시 |
| `types/component-events.ts` 옛 컴포넌트 발행/구독 주석 | 코드 동작 영향 없음 (주석만) | 별도 type cleanup 시 |
| `V2TableListConfigPanel.tsx` + `V2ListConfigPanel.tsx` | `InvDataConfigPanel` 이 v2-list / v2-table-list canonical 경로에서 사용 | v2-list / v2-table-list 폐기 후 |
| `V2RepeatContainerConfigPanel.tsx` | `v2-repeat-container/index.ts` ComponentDefinition.config_panel | v2-repeat-container 폐기 후 |
| `V2SplitPanelLayoutConfigPanel.tsx` | `v2-split-panel-layout/index.ts` ComponentDefinition.config_panel | split-panel-layout 폐기 후 |
| `lib/registry/components/v2-split-panel-layout/SplitPanelLayoutConfigPanel.tsx` | `getComponentConfigPanel.CONFIG_PANEL_MAP` v2-split-panel-layout 직접 import | split-panel-layout 폐기 후 |
| `componentConfig.ts` 의 v2-table-list / v2-split-panel-layout / v2-repeat-container / v2-repeater schema | auto-register 보존 항목과 짝. 새 생성 시 ComponentDefinition.default_config 와 다를 수 있음 (이중 source) | 위 폴더 폐기 시 |

---

## 5. Verification

| Command | Result |
|---|---|
| `git diff --check` | **pass** (whitespace 오류 0건) |
| `rg "v2-input\|v2-select\|V2InputRenderer\|V2SelectRenderer" frontend/lib frontend/components frontend/app frontend/types frontend/styles` | **0건** (acceptance pass) |
| `rg "EntityPicker\|entity-picker\|EntitySearchModal" frontend/lib/registry/components/input frontend/components/v2/config-panels/InvFieldConfigPanel.tsx` | **0건** (acceptance pass) |
| `rg "v2-aggregation-widget\|v2-status-count\|aggregation-widget" frontend/lib frontend/components frontend/app frontend/types` | 50 → **40건** (10건 감소). 잔여: alias 9건 (런타임 라우팅, §3) + 주석/문서 25건 (영향 없음) + i18n 5건 (hard blocker) + 테스트 페이지 3건 (hard blocker, §4) |
| `rg "v2-table-list\|table-list\|v2-split-panel-layout\|split-panel-layout\|split-panel-layout2" frontend/lib frontend/components frontend/app frontend/types` | 232 → **216건** (16건 감소). 잔여 대부분: table-list/ + v2-table-list/ + split-panel-layout/ 보존 폴더 내부 참조 (FlowWidget shared / master-detail UX hard blocker §4) |
| `rg "v2-tabs-widget\|tabs-widget\|v2-section-card\|v2-section-paper\|section-card\|section-paper\|v2-repeat-container\|repeat-container\|v2-repeater" frontend/lib frontend/components frontend/app frontend/types` | 164 → **138건** (26건 감소). 잔여 대부분: repeat-container/ + v2-repeat-container/ + v2-repeater/ + accordion-basic/ + conditional-container/ 보존 폴더 내부 참조 (canonical skeleton 부족 hard blocker §4) + InvyoneStudio canonical 인식 분기 |
| `cd backend-spring && ./gradlew compileJava` | **BUILD SUCCESSFUL** (UP-TO-DATE, backend 변경 0건) |
| 삭제된 폴더 외부 import 잔여 | **0건** (`from "@/lib/registry/components/(aggregation-widget\|tabs/\|v2-tabs-widget\|section-card\|v2-section-card\|section-paper\|v2-section-paper\|v2-aggregation-widget\|v2-status-count)"` rg) |
| 삭제된 V2 ConfigPanel 잔여 참조 | **0건** (`V2AggregationWidgetConfigPanel\|V2StatusCountConfigPanel` rg) |
| FieldConfig / DataPort 계약 | `frontend/types/invyone-component.ts` 변경 0건 |

---

## 6. 잔여 매칭 카테고리별 분류

### 6.1 Stats (40건)

- **alias 라우팅 (코드 동작) — 9건**: `templateMigrate.ts` (3), `DynamicComponentRenderer.tsx` (3), `getComponentConfigPanel.CONFIG_PANEL_ALIAS` (3). 모두 옛 ID → canonical `stats` 알리아싱 — runtime migration boundary (구현된 canonical replacement)
- **주석/문서 — 25건**: 제거 작업 흔적 주석 (componentConfig.ts, registry/components/index.ts, getComponentConfigPanel.tsx, withContainerQuery.css), 흡수 대상 문서 (stats/index.ts, stats/types.ts), 옛 ID 주석 (ComponentsPanel hiddenComponents — ComponentRegistry 에 없으므로 동작 영향 0), 발행/구독 주석 (component-events.ts), 옛 컴포넌트 알림 주석 (UnifiedRepeater.tsx). 코드 동작 영향 0
- **i18n hard blocker — 5건**: `multilangLabelExtractor.ts` (2), `MultilangSettingsModal.tsx` (3). 옛 멀티랭 데이터 추출 보존
- **테스트 페이지 — 3건**: `test-card-responsive/page.tsx` 옛 ResizeObserver 패턴 데모

### 6.2 Table (216건)

- **table-list/ + v2-table-list/ 보존 폴더 내부 — 약 180건**: FlowWidget (SingleTableWithSticky/ColumnConfig) hard blocker §4
- **split-panel-layout/ + v2-split-panel-layout/ + split-panel-layout2/ 보존 폴더 내부 — 약 25건**: master-detail UX hard blocker §4
- **canonical 인식 추가됨 — 11건**: ActionTab (4) + DataTab (4) + InvLegacyButtonConfigPanel (2) + ButtonPrimaryComponent (1). 옛 ID 와 canonical "table" 함께 매칭 (런타임 호환)
- **InvyoneStudio split-panel drag/drop 분기**: 옛 화면 호환 hard blocker §4

### 6.3 Container (138건)

- **repeat-container/ + v2-repeat-container/ + v2-repeater/ + accordion-basic/ + conditional-container/ 보존 폴더 내부 — 약 100건**: canonical container.repeater/accordion/conditional skeleton 부족 hard blocker §4
- **InvyoneStudio canonical container 인식 분기 — 9건**: canonical container (containerType=tabs) 인식 추가됨 (구현된 canonical replacement)
- **InteractiveScreenViewer / TabsWidget canonical 인식 — 2건**: 동일
- **buttonActions.ts 옛 v2-repeater 처리 — 2건**: hard blocker §4
- **container/types.ts 흡수 대상 문서 — 5건**: canonical container 자체

---

## 7. Acceptance check 요약 (2026-05-19 후속 수정 기준)

| 조건 | 결과 |
|---|---|
| new Studio creation paths use canonical | ✅ palette BASIC_IDS 만 canonical / advanced 노출 |
| FieldConfig / DataPort contracts preserved | ✅ 변경 0건 |
| v2-input/v2-select / EntityPicker / EntitySearchModal 재도입 X | ✅ rg 0건 |
| `git diff --check` | ✅ pass (EXIT 0) |
| backend `./gradlew compileJava` | ✅ BUILD SUCCESSFUL |
| `rg "v2-input\|v2-select\|V2InputRenderer\|V2SelectRenderer"` (frontend/lib + components + app + types + styles) | ✅ 0건 |
| `rg "EntityPicker\|entity-picker\|EntitySearchModal"` (input + InvFieldConfigPanel) | ✅ 0건 |
| `rg` 삭제 폴더 import (`aggregation-widget`/`v2-aggregation-widget`/`v2-status-count`/`tabs`/`v2-tabs-widget`/`section-card`/`v2-section-card`/`section-paper`/`v2-section-paper`) | ✅ 0건 |
| `test ! -e` 9개 legacy 폴더 | ✅ ALL 9 FOLDERS DELETED OK |
| `git diff --quiet -- frontend/components/control frontend/components/dash frontend/styles/control-mode.css` | ✅ EXIT 0 (range-out 변경 모두 제거) |
| `frontend/components/control/controlDemo.ts` (= 실제 `controlPreviewData.ts`) 미존재 | ✅ No such file or directory |
| 잔여 매칭 카운트 (사용자 acceptance 패턴 기준) | stats 패턴 **4건** / table 패턴 **4건** / container 패턴 **4건** |
| 남은 매칭 모두 (1) 구현된 canonical replacement / (2) 삭제된 path / (3) concrete blocker / (4) domain preserved 로 분류 | ✅ §3 / §2 / §4 / §9 |

---

## 8. Remaining risks

1. **canonical container.containerType=accordion / repeater / conditional 미구현**:
   skeleton 만 있음. 새 화면에서 container 를 만들고 containerType 을 이들로 변경
   하면 동작 부족. palette 에 container 는 한 가지로 노출되므로 사용자 혼동 가능.
   → Phase G.x 추가 작업 필요 (canonical container 완성).

2. **canonical table displayMode='split' 의 leftPanel/rightPanel UX 부재**:
   옛 split-panel-layout 의 master-detail (drag/drop, selection sync, resize,
   nested child) 없음. 새 화면에서 split 모드 사용 시 기능 부족.

3. **옛 v2-aggregation-widget / v2-status-count config 호환 미보장**:
   auto-register 제거 + 폴더 삭제 → ComponentRegistry 에 없음. alias 로 canonical
   stats 라우팅되지만 config 형식이 다르므로 빈 stats 렌더링. 메모리
   `project_solution_definition_phase` 에 따라 round-trip 호환 미보장 — 사용자가
   옛 화면 재설계 필요.

4. **frontend 전체 typecheck / lint 미실행**: 변경 파일에 import 잔여 0건 확인,
   삭제 파일 사용처 0건 확인. 하지만 pre-existing typecheck 오류 가능성 (이전부터
   존재). 변경 폴더 (lib/registry/components/, components/screen/, lib/utils/) 에는
   새 import-not-found 오류 0건 보장.

5. **InvyoneStudio.tsx split-panel/tabs-widget drag/drop 분기 보존**: 옛 화면 호환
   유지. split-panel-layout 폐기 시 InvyoneStudio drag/drop 분기도 같이 정리.

6. **multilangLabelExtractor.ts / MultilangSettingsModal.tsx aggregation-widget i18n**:
   옛 멀티랭 데이터 추출 보존. canonical stats 로 마이그레이션 시 별도 작업 필요.

---

## 9. Codex 후속 검증 — 범위 밖 변경 제거 (2026-05-19)

Codex 검증에서 canonical cleanup 범위 (이 문서 본문 + 이전 보고서 §2~§4) 를 넘어
`frontend/components/control/` 와 `frontend/components/dash/` 의 데모/대시보드 변경,
그리고 `frontend/styles/control-mode.css` 의 flow-active styling 확장이 발견됨.
이번 cleanup 결과에는 포함하면 안 되는 범위 밖 작업.

### 9.1 제거 사유

- canonical data-view cleanup 의 스코프 (보존해야 하는 canonical 파일 + 의도된 삭제
  대상) 와 무관.
- `controlPreviewData.ts` (= prompt 의 `controlDemo.ts`) 안에 `previewField`,
  `ORDER_ID`, `USER_ID`, `DEPT_CODE` 같은 데모 fixture 가 있음.
- `RuleBuilder.tsx`, `ControlPalette.tsx`, `DashboardCanvas.tsx`,
  `control-mode.css` 에서 `controlPreviewData` / `previewData` / `flow-active` 등
  데모 패턴 import 가 확인됨.
- `git diff --check` 와 acceptance command 자체는 통과하지만 PR scope 가 잘못 부풀어
  있어 분리해야 함.

### 9.2 변경 전 사전 검증

`git diff -- frontend/components/control frontend/components/dash frontend/styles/control-mode.css` 출력을 다음 패턴으로 grep:

```
stats|grouped-table|card-list|FieldConfig|DataPort|sourceProvider|dataReceiver|
aggregation-widget|v2-table-list|v2-status-count|v2-tabs-widget|v2-section|
EntityPicker|EntitySearchModal|InvStatsConfigPanel|InvTableConfigPanel|
InvContainerConfigPanel|INVYONE_UNIFIED_IDS|CONFIG_PANEL_ALIAS|LEGACY_TO_UNIFIED|
fillParentTypes|fullWidthComponents
```

결과: `FieldConfig` 만 매칭 — control/dash 의 demo TableMeta 타입 (`type TableMeta = { columns: FieldConfig[] }`) 정의에서 type 으로 import 한 것뿐. canonical
data-view cleanup 의 직접 변경 (alias 등록 / schema 삭제 / ConfigPanel 라우팅) 과
무관. `FieldConfig` 타입 자체 (frontend/types/invyone-component.ts) 도 변경 0건. →
revert 안전 확인.

### 9.3 되돌린 파일 (9 tracked + 1 untracked)

`git restore` (HEAD 로 복원):

- `frontend/components/control/ControlMode.tsx`
- `frontend/components/control/ControlPalette.tsx`
- `frontend/components/control/FlowViewer.tsx`
- `frontend/components/control/PortHandle.tsx`
- `frontend/components/control/RuleBuilder.tsx`
- `frontend/components/control/hooks/useControlMode.ts` (prompt 명시 8개 외 +1. CTRL_NODE_TYPES prettier 포맷팅만 — canonical 무관. acceptance check `git diff --quiet -- frontend/components/control` 통과 위해 동일 범위 밖 변경으로 함께 restore)
- `frontend/components/dash/DashboardCanvas.tsx`
- `frontend/components/dash/DashboardLayout.tsx`
- `frontend/styles/control-mode.css`

`rm`:

- `frontend/components/control/controlPreviewData.ts` (= prompt 의 `controlDemo.ts`. untracked. demo fixture)

### 9.4 보존된 canonical cleanup 파일 (변경 그대로 유지)

- `frontend/lib/registry/DynamicComponentRenderer.tsx`
- `frontend/lib/registry/components/index.ts`
- `frontend/lib/utils/getComponentConfigPanel.tsx`
- `frontend/lib/utils/responsiveDefaults.ts`
- `frontend/lib/utils/templateMigrate.ts`
- `frontend/lib/schemas/componentConfig.ts`
- `frontend/types/v2-components.ts`
- `frontend/lib/registry/hoc/withContainerQuery.css`
- `frontend/components/screen/config-panels/button/DataTab.tsx`
- `frontend/components/screen/config-panels/button-config/ActionTab.tsx`
- `frontend/components/v2/config-panels/InvLegacyButtonConfigPanel.tsx`
- `frontend/components/screen/InvyoneStudio.tsx`
- `frontend/components/screen/RealtimePreviewDynamic.tsx`
- `frontend/components/screen/ScreenNode.tsx`
- `frontend/components/screen/InteractiveScreenViewer.tsx`
- `frontend/components/screen/widgets/TabsWidget.tsx`
- `frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx`
- 의도된 9개 폴더 + 2개 V2 ConfigPanel 의 삭제 상태 유지

### 9.5 후속 잔여 매칭 분류 (사용자 acceptance 패턴)

| 패턴 | 카운트 | 분류 |
|---|---|---|
| `stats\|aggregation-widget\|v2-status-count` | 4건 | (1) 구현된 canonical replacement: `templateMigrate.LEGACY_TO_UNIFIED` / `DynamicComponentRenderer.LEGACY_TO_UNIFIED` / `getComponentConfigPanel.CONFIG_PANEL_ALIAS` alias 라우팅 + canonical `stats/index.ts` 흡수 대상 문서 |
| `table-list\|v2-table-list` | 4건 | (3) concrete hard blocker: `lib/registry/components/table-list/` (FlowWidget SingleTableWithSticky 직접 import) + `lib/registry/components/v2-table-list/` 보존 폴더 내부 참조 |
| `tabs-widget\|v2-tabs-widget\|section-card\|section-paper\|accordion-basic\|conditional-container\|repeat-container\|v2-repeat-container\|split-panel-layout` | 4건 | (3) hard blocker: `accordion-basic/` + `conditional-container/` + `repeat-container/` + `split-panel-layout` 3종 (canonical container skeleton 부족 / master-detail UX 다름 / SplitPanelContext provider) 보존 폴더 내부 참조 |

> 4 + 4 + 4 = 12 잔여 모두 §3 (구현된 canonical replacement, alias 라우팅) 또는
> §4 (hard blocker 보존 폴더 내부 참조) 중 하나로 분류 — 이 cleanup 의 범위 안에서
> 남은 dangling 매칭 0건.

---

## 10. 2026-05-19 추가 — Canonical Table-Like Helper 도입

§3 의 "ActionTab / DataTab / InvLegacyButtonConfigPanel / ScreenNode / InteractiveScreenViewer / TabsWidget canonical 인식 추가" 와 같은 흐름을 더 외부로 확장.
`componentType === "table-list"` 단독 체크가 남아 있던 외부 active branch 를
**canonical-aware helper** 로 일괄 교체했다.

> ★ 이번 단계는 **런타임 폴더 / schema / auto-register 삭제 아님.** 옛 화면 호환
> hard blocker (§4 의 table-list/, v2-table-list/, componentConfig schema, registry
> auto-register import) 는 모두 그대로 보존.

### 10.1 신규 helper (`frontend/lib/utils/componentTypeUtils.ts`)

세 가지 helper 를 같은 파일 끝에 새 block 으로 추가:

| Helper | 시그니처 | 인식 토큰 |
|---|---|---|
| `isTableLikeComponentType(typeValue)` | `(typeValue: unknown) => boolean` | `table` (canonical), `table-list`, `v2-table-list`, `data-table`, `datatable`. URL 등 prefix 가 붙으면 마지막 segment 도 fallback 검사 |
| `isTableLikeComponent(component)` | `(component: unknown) => boolean` | 객체에서 `componentType` / `component_type` / `type` / `widgetType` / `widget_type` / `componentConfig.type` / `component_config.type` / `url` 후보 모두 검사. 어느 하나라도 table-like 면 true |
| `getTableNameFromTableLikeComponent(component)` | `(component: unknown) => string \| undefined` | `componentConfig.selectedTable` → `componentConfig.tableName` → `componentConfig.table_name` → `component_config.selectedTable` → `component_config.tableName` → `component_config.table_name` → root `tableName` → root `table_name` 순으로 안전하게 추출 |

비식별 토큰 모음을 따로 두지 않고 `TABLE_LIKE_COMPONENT_TYPES: ReadonlySet<string>`
한 곳에서 단일 source 로 관리.

### 10.2 helper 사용처 (외부 active branch 교체 결과)

| 파일 | 구 구현 | 신 구현 |
|---|---|---|
| `lib/utils/buttonActions.ts` `autoDetectDataSource` (구 line ~3267) | `comp.componentType === "table-list" && comp.componentConfig?.tableName` 단독 분기 | `isTableLikeComponent(comp) && getTableNameFromTableLikeComponent(comp)` — canonical `table` / hidden `v2-table-list` 도 dataSource 자동 감지 대상 |
| `lib/utils/buttonActions.ts` `handleOpenModalWithData` (구 line ~3465) | 동일 패턴 | 동일 helper 호출. 로그 메시지 "TableList 자동 감지" → "Table-like 컴포넌트 자동 감지" 로 일반화 |
| `lib/utils/buttonActions.ts` Excel `findTableListComponent` (구 line ~5283) | `comp.componentType === "table-list"` + 수동 `selectedTable` / `tableName` OR 체인 | `isTableLikeComponent(comp)` + `getTableNameFromTableLikeComponent(comp) === context.table_name` |
| `app/(main)/screens/[screenId]/page.tsx` `hasTableWidget` (구 line 431) | `componentType === "table-list" \|\| componentType === "v2-table-list" \|\| widgetType === "table"` OR 체인 | `isTableLikeComponent(comp)`. canonical `table` 도 동일하게 자동 로드 skip |
| `components/screen/widgets/TabsWidget.tsx` `screenInfoMap` (구 line 146) | `c.component_type === "table" \|\| === "v2-table-list" \|\| === "table-list"` OR 체인 + `tableComp?.component_config?.selectedTable` 수동 추출 | `isTableLikeComponent(c)` + `getTableNameFromTableLikeComponent(tableComp)`. snake_case `component_type` 누락 위험도 동시 해소 |
| `components/screen/modals/MultilangSettingsModal.tsx` `getTypeIcon` (구 line 154) | `case "table-list": <Table2 />` 단독 | `case "table" / "table-list" / "v2-table-list"` fallthrough 묶음. 기존 호환 유지 |
| `components/screen/modals/MultilangSettingsModal.tsx` `NON_INPUT_COMPONENT_TYPES` (구 line 196) | `"table-list"` 만 Set 에 | `"table"`, `"v2-table-list"` 추가. canonical / hidden legacy 모두 라벨 다국어 처리 제외 |
| `components/screen/panels/ComponentsPanel.tsx` (주석만) | `allComponents` 위 주석 + `hiddenComponents` `table-list` 주석이 "→ v2-table-list" 식으로 옛 매핑 표기 | "canonical `table` 이 새 생성 경로, v2-table-list 는 hidden legacy 등록 (registry hard blocker 보존)" 으로 명확화. hidden list 자체는 유지 |

### 10.3 보존한 Hard Blocker (이번 단계에서 의도적으로 건드리지 않음)

| 위치 | 보존 이유 |
|---|---|
| `lib/registry/components/v2-table-list/**`, `lib/registry/components/table-list/**` | §4 와 동일. 옛 저장 화면에서 `componentType: "v2-table-list"` / `"table-list"` 를 직접 가리키는 layout JSON 다수 — 런타임 렌더 미보장 시 화면 깨짐 |
| `lib/registry/components/index.ts` 의 `table-list` / `v2-table-list` auto-register import | ComponentRegistry 등록이 빠지면 옛 화면 로드 실패. 삭제 금지 |
| `lib/schemas/componentConfig.ts` 의 `v2-table-list` schema / default | old layout load compatibility (`componentConfig` schema 검증을 통과해야 함). 삭제 금지 |
| `buttonActions.ts` 의 `split-panel-layout` 분기 (line ~3293, ~3477) | table-like 와는 별개 컴포넌트. `componentConfig.leftPanel.tableName` 라는 고유 경로 사용. helper 적용 대상 아님 → §4 와 동일하게 보존 |
| `frontend/components/control/**`, `frontend/components/dash/**`, `frontend/styles/control-mode.css` | 본 cleanup 범위 외 (§9 의 control/dash revert 와 동일 정책) |
| 금지 토큰 (`v2-input`, `v2-select`, `V2InputRenderer`, `V2SelectRenderer`, `EntityPicker`, `entity-picker`, `EntitySearchModal`) 재도입 금지 | 본 작업에서 재도입 0건 (§10.4 acceptance) |

### 10.4 Acceptance (이번 단계)

| 검증 항목 | 명령 | 결과 |
|---|---|---|
| Whitespace clean | `git diff --check` | ✅ pass (출력 없음) |
| 금지 토큰 (input canonical) | `rg "v2-input\|v2-select\|V2InputRenderer\|V2SelectRenderer" frontend/lib frontend/components frontend/app frontend/types frontend/styles` | ✅ 0건 |
| EntityPicker 잔재 | `rg "EntityPicker\|entity-picker\|EntitySearchModal" frontend/lib/registry/components/input frontend/components/v2/config-panels/InvFieldConfigPanel.tsx` | ✅ 0건 |
| 3개 파일 `componentType === "table-list"` 단독 분기 | `rg 'componentType === "table-list"' frontend/lib/utils/buttonActions.ts "frontend/app/(main)/screens/[screenId]/page.tsx" frontend/components/screen/widgets/TabsWidget.tsx` | ✅ 0건 |
| backend-spring 컴파일 | `cd backend-spring && ./gradlew compileJava` | ✅ BUILD SUCCESSFUL |
| 변경 파일 TS 신규 에러 | `npx tsc --noEmit` — 본 단계에서 손댄 line 들 | ✅ 0건 신규 에러 (helper 추가 line, OR 체인 교체 line 모두 깨끗. 기존 에러는 본 cleanup 범위 외라 유지) |

### 10.5 변경 파일 요약 (이번 단계)

```
M frontend/lib/utils/componentTypeUtils.ts      (+helper block 100여 줄)
M frontend/lib/utils/buttonActions.ts           (3곳 helper 호출로 교체, +1 import)
M frontend/app/(main)/screens/[screenId]/page.tsx  (hasTableWidget 한 줄 helper 호출, +1 import)
M frontend/components/screen/widgets/TabsWidget.tsx (screenInfoMap helper 호출, +1 import)
M frontend/components/screen/modals/MultilangSettingsModal.tsx (icon/Set 분류만)
M frontend/components/screen/panels/ComponentsPanel.tsx (주석 정리만)
M notes/gbpark/2026-05-19-canonical-data-view-cleanup-followup.md (본 §10)
```

### 10.6 후속 작업 후보 (이번 범위 밖)

- v2-table-list / table-list 폴더의 실제 폐기 — old layout 마이그레이션이 끝난 뒤
  §4 hard blocker 와 함께 별도 phase 로 진행
- `componentConfig.ts` schema / default 의 v2-table-list 제거 — 동일 조건
- `buttonActions.ts` 의 `split-panel-layout` 분기 — canonical table.displayMode='split'
  master-detail UX 완성 후 §4 와 함께 마이그레이션
- 기존 page.tsx / buttonActions.ts 의 기존 TS 에러 (snake_case vs camelCase 타입 불일치)
  정리 — 본 cleanup 과 무관한 별도 트랙

---

## 11. 2026-05-19 추가 (2차) — Helper 확산 (button/preview/backend)

§10 에서 도입한 helper 를 **외부 active branch 8 frontend + 2 backend** 까지 확산.
런타임 보존 폴더 / schema / auto-register 는 그대로 유지하고 active 분기만 교체.

### 11.1 변경 파일 (10개)

| 파일 | 변경 요지 |
|---|---|
| `frontend/components/screen/config-panels/button/DataTab.tsx` | 로컬 `isDataTransferComponentType` 정의를 `isTableLikeComponentType` 기반으로 정리. table-like 외 `repeater-field-group` / `form-group` 만 별도 `DATA_TRANSFER_EXTRA_PATTERNS` 로 보존 |
| `frontend/components/screen/config-panels/button-config/ActionTab.tsx` | 동일한 로컬 helper 정리 + `compType === "table-list"` 단독 분기 2곳 (`autoDetect` modal source 감지, `openModalWithData` 소스 감지) 을 `isTableLikeComponent` + `getTableNameFromTableLikeComponent` 로 교체. `compId === "table-list"` OR 체인 (테이블명 폴백) 도 `isTableLikeComponentType(compId)` 로 정리. `split-panel-layout` / `screen-split-panel` / `split-panel-layout2` 는 별개 컴포넌트라 그대로 보존 |
| `frontend/components/v2/config-panels/InvLegacyButtonConfigPanel.tsx` | 동일한 로컬 `isDataTransferComponentType` 정리 |
| `frontend/components/screen/ScreenNode.tsx` | `componentKind === "table-list"` 포함 OR 체인을 `isTableLikeComponentType(componentKind)` + `TABLE_LIKE_EXTRA_KINDS` (grouped-table / card-list / data-grid) 로 정리. 미니어처 색상 결정용 |
| `frontend/components/screen/RealtimePreviewDynamic.tsx` | `fillParentTypes` 배열의 `table` / `table-list` / `v2-table-list` 를 helper 로 통합 + 그 외 split/tabs layout 만 명시 목록 유지. `componentConfig.type === "table-list"` 단독 분기 2곳 (size.height 최소 200 보장, 기본 200 fallback) 도 helper 호출로 교체 |
| `frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx` | `provider.component_type === "table-list"` 단독 탐색 → `isTableLikeComponentType(provider.component_type)` |
| `frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx` | 동일한 패턴 + DataReceiver 자동 탐색의 `rt === "table" \|\| === "table-list" \|\| === "data-table"` OR 체인 → helper 한 호출 |
| `frontend/lib/registry/components/universal-form-modal/UniversalFormModalConfigPanel.tsx` | `compType === "table-list" \|\| === "interactive-data-table"` OR 체인 → `isTableLikeComponentType(compType) \|\| compType === "interactive-data-table"` |
| `backend-spring/src/main/java/com/erp/service/ScreenGroupService.java` | `private static int countTableLikeWidgets(Map<String,Integer>)` 헬퍼 신규 — `table` + `table-list` + `v2-table-list` 합산. `screenType = "grid"` 추론 분기 2곳 (`extractScreenLayout` / `getScreenSummary`) 모두 helper 사용. 셋 중 어느 것이든 있으면 grid |
| `backend-spring/src/main/resources/mapper/screenGroup.xml` | `selectSavableScreensForGroup` 의 `NOT EXISTS` 서브쿼리에서 `componentType = 'table-list'` 단독을 `IN ('table', 'table-list', 'v2-table-list')` 로 확장. 체크박스 활성 판정도 legacy `componentConfig.checkbox.enabled` 와 canonical `componentConfig.showCheckbox` 를 SQL OR 로 구분 처리 |

### 11.2 보존한 Hard Blocker (이번 단계에서 의도적으로 건드리지 않음)

| 위치 | 보존 이유 |
|---|---|
| `lib/registry/components/v2-table-list/**`, `lib/registry/components/table-list/**` | §4 / §10.3 와 동일. 옛 저장 layout JSON 의 직접 type 지목 — 폴더 삭제/대형 수정 시 화면 깨짐 |
| `lib/schemas/componentConfig.ts` 의 v2-table-list schema / default | old layout config 검증 통과 hard blocker. 삭제 금지 |
| `lib/registry/components/repeat-container/**` 의 `dataSourceType = "table-list"` enum | **컴포넌트 type 이 아니라 데이터 소스 모드 enum 값**. helper 적용 대상 아님. 같은 문자열이지만 도메인이 다름 → 이번 범위에서 건드리지 않음 |
| `frontend/components/v2/config-panels/InvDataConfigPanel.tsx` 의 `v2-table-list` 참조 | old layout config hard blocker. `InvDataConfigPanel` 이 v2-list / v2-table-list canonical 경로에서 사용 (§4 참조). 보존 |
| `ActionTab.tsx` / `RealtimePreviewDynamic.tsx` 의 `split-panel-layout` / `split-panel-layout2` / `screen-split-panel` / `v2-tab-container` / `tabs-widget` 등 분기 | table-like 와 별개 컴포넌트. `componentConfig.leftPanel.*` / `componentConfig.tabs.*` 등 고유 path 사용. helper 적용 대상 아님 |
| `frontend/components/control/**`, `frontend/components/dash/**`, `frontend/styles/control-mode.css` | §9 정책과 동일. 본 cleanup 범위 외 |
| 금지 토큰 (`v2-input`, `v2-select`, `V2InputRenderer`, `V2SelectRenderer`, `EntityPicker`, `entity-picker`, `EntitySearchModal`) | 본 작업에서 재도입 0건 |

### 11.3 Acceptance (이번 단계)

| 검증 항목 | 명령 | 결과 |
|---|---|---|
| Whitespace clean | `git diff --check` | ✅ pass (출력 없음) |
| 금지 토큰 (input canonical) | `rg "v2-input\|v2-select\|V2InputRenderer\|V2SelectRenderer" frontend/lib frontend/components frontend/app frontend/types frontend/styles` | ✅ 0건 |
| EntityPicker 잔재 | `rg "EntityPicker\|entity-picker\|EntitySearchModal" frontend/lib/registry/components/input frontend/components/v2/config-panels/InvFieldConfigPanel.tsx` | ✅ 0건 |
| 10개 수정 대상 파일 `componentType === "table-list"` 단독 분기 | `rg 'componentType === "table-list"\|component_type === "table-list"'` (수정 대상 8개 frontend 파일 한정) | ✅ 0건 |
| backend-spring 컴파일 | `cd backend-spring && ./gradlew compileJava` | ✅ BUILD SUCCESSFUL |
| 변경 파일 TS 신규 에러 | `npx tsc --noEmit` — 본 단계 손댄 line | ✅ 0건 신규 에러 (helper 추가/교체 line 모두 깨끗. 기존 에러는 §10.4 와 동일하게 본 범위 외라 유지) |

### 11.4 helper 사용 사이트 누적 (§10 + §11)

| Helper | 사용처 |
|---|---|
| `isTableLikeComponentType(typeValue)` | `componentTypeUtils.ts` 내부 (`isTableLikeComponent` 의 부품), `DataTab.tsx`, `ActionTab.tsx`, `InvLegacyButtonConfigPanel.tsx`, `ScreenNode.tsx`, `RealtimePreviewDynamic.tsx`, `ButtonPrimaryComponent.tsx` (×2), `UniversalFormModalConfigPanel.tsx` |
| `isTableLikeComponent(component)` | `buttonActions.ts` (×3), `screens/[screenId]/page.tsx`, `TabsWidget.tsx`, `ActionTab.tsx` (×2) |
| `getTableNameFromTableLikeComponent(component)` | `buttonActions.ts` (×3), `TabsWidget.tsx`, `ActionTab.tsx` (×2) |

### 11.5 후속 작업 후보 (이번 범위 밖)

- canonical table 의 `showCheckbox` config 실제 검증 — `screenGroup.xml` 의 OR 분기가
  canonical 화면 SQL 결과에 미치는 영향 확인 (실 데이터 검증 필요)
- `ActionTab.tsx` 의 `v2-list` 분기 — 별도 컴포넌트이므로 v2-list 폐기 시 같이 정리
- `ScreenNode.TABLE_LIKE_EXTRA_KINDS` (grouped-table / card-list / data-grid) — canonical
  컴포넌트군 정착 후 `TABLE_LIKE_COMPONENT_TYPES` 본체로 흡수 검토
- `repeat-container` 의 `dataSourceType = "table-list"` enum naming — 도메인 분리 위해
  `tableList` → `legacyTableList` 등으로 rename 검토 (별도 트랙)
