# 2026-05-18 Canonical Data View Cleanup — Final Report

Goal 원본: `notes/gbpark/2026-05-18-canonical-data-view-goal.md`

작업 범위: INVYONE Studio 의 `stats`, `table`, `container`, `chart`, `card-list`,
`grouped-table` 6개 데이터뷰 계열을 input canonical 작업과 동일한 수준으로 정리.
새 생성 경로는 canonical ID 로 수렴, legacy/V2 경로는 migration adapter / domain
preserved / shared dependency 로 명시적 분류.

---

## 1. Summary

- canonical chart / card-list / grouped-table 을 first-class 진입점에 추가
  (`DynamicComponentRenderer.INVYONE_UNIFIED_IDS`,
  `responsiveDefaults.fullWidthComponents`,
  `templateMigrate.MAIN_CONTENT_IDS / FULL_WIDTH_IDS / inferPolicy`).
- canonical container (`containerType: "tabs"`) 를 Studio drag/drop, selection,
  helper 9 곳 모두 옛 `tabs-widget` / `v2-tabs-widget` 과 동일하게 처리하도록 추가.
- canonical table 을 button data transfer (`ActionTab` / `DataTab` /
  `InvLegacyButtonConfigPanel`) 및 `ButtonPrimaryComponent` data provider /
  receiver 자동탐색에 인식시킴.
- `RealtimePreviewDynamic.fillParentTypes` 에 canonical table / container /
  grouped-table / card-list 추가 (런타임 부모 100% 채움).
- `ScreenNode.tsx` 색상 분기, `InteractiveScreenViewer.tsx` 탭 처리,
  `TabsWidget.tsx` inline 테이블 탐색에 canonical 인식 추가.
- legacy `aggregation-widget` 의 `hidden: true` 활성화 (palette 봉인).
- FieldConfig / DataPort 계약 변경 0 건. canonical stats / table / chart /
  card-list / grouped-table 의 `dataPorts` 선언 그대로 유지.

---

## 2. Canonical paths updated

| 파일 | 변경 |
|---|---|
| `frontend/lib/registry/DynamicComponentRenderer.tsx` | `INVYONE_UNIFIED_IDS` 에 `chart` / `card-list` / `grouped-table` 추가. v2- 자동 매핑이 엉뚱한 컴포넌트로 라우팅하지 않도록 차단 |
| `frontend/lib/utils/responsiveDefaults.ts` | `fullWidthComponents` 에 `table` / `grouped-table` / `card-list` 추가 (chart 는 자연스러운 작은 크기도 자주 사용되므로 제외) |
| `frontend/lib/utils/templateMigrate.ts` | `FULL_WIDTH_IDS` 에 `grouped-table`, `MAIN_CONTENT_IDS` 에 `chart` / `card-list`, `inferPolicy` 에 `chart` / `card-list` → `'reflow'` |
| `frontend/lib/registry/components/aggregation-widget/index.ts` | `hidden: true` 활성화. registry 등록은 유지 (옛 화면 alias 라우팅용) |
| `frontend/components/screen/config-panels/button/DataTab.tsx` (×4) | data transfer 후보 매칭에 canonical `"table"` 추가 |
| `frontend/components/screen/config-panels/button-config/ActionTab.tsx` (×4) | 동일 패턴 적용 |
| `frontend/components/v2/config-panels/InvLegacyButtonConfigPanel.tsx` (×2) | 동일 패턴 적용 |
| `frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx` | `sourceProvider` 자동탐색 + `targetReceiver` 자동탐색에 canonical `"table"` 추가 |
| `frontend/components/screen/RealtimePreviewDynamic.tsx` | `fillParentTypes` 에 canonical 4 종 추가 |
| `frontend/components/screen/InvyoneStudio.tsx` (×9) | `tabs-widget` / `v2-tabs-widget` 분기 모두에 canonical container (`containerType === "tabs"`) 인식 추가 |
| `frontend/components/screen/ScreenNode.tsx` | 미니어처 색상 분기에 canonical `table` / `grouped-table` / `card-list` 추가 |
| `frontend/components/screen/InteractiveScreenViewer.tsx` | 탭 렌더링 분기에 `isCanonicalTabsContainer` 추가 |
| `frontend/components/screen/widgets/TabsWidget.tsx` | inline 테이블 탐색에 canonical `"table"` 추가 |

---

## 3. Deleted

폴더 자체 삭제 0 건. 본 cleanup 의 목적은 "삭제" 가 아니라 "분류" 임 (Goal §3
원칙). 새 생성 경로가 canonical 로만 수렴하고, 옛 ID 는 alias / migration adapter
/ domain shared 로 명시.

---

## 4. Preserved legacy/V2 adapters

| Component / path | Reason | Next condition for deletion |
|---|---|---|
| `lib/registry/components/aggregation-widget/` | 옛 저장 layout alias → canonical `stats` 로 라우팅. palette hidden 활성화됨 | DB 상 `aggregation-widget` 사용 화면 0 건이 확인되면 폴더 삭제 가능 |
| `lib/registry/components/v2-aggregation-widget/` | items 단위 `dataSource` 매핑이 1:1 아님 (전역 `filters` vs 항목별 `OptionFilter`). 새 생성은 canonical `stats`. palette hidden | items × filters 매핑 어댑터 구현 후 폴더 삭제 가능 |
| `lib/registry/components/v2-status-count/` | `relationColumn` + `parentColumn` 의 부모 row 컨텍스트가 canonical stats DataPort 로 전달되지 않음 (Goal §3.1 stop condition). palette hidden | stats 가 `parentRow(row)` DataPort 를 받도록 확장하거나 OptionFilter `value_type: "field"` 가 외부 row 를 받도록 인프라 보강 후 삭제 |
| `lib/registry/components/table-list/` | `FlowWidget` 이 `SingleTableWithSticky` + `ColumnConfig` 를 직접 import (Goal §8.1) | FlowWidget 을 canonical table 기반으로 마이그레이션하거나 `SingleTableWithSticky` 를 shared 위치로 추출 후 삭제 |
| `lib/registry/components/v2-table-list/` | 옛 저장 layout alias → canonical `table` 로 라우팅. palette hidden | DB 상 `v2-table-list` 사용 화면 0 건 + canonical table 의 column ordering / export / context menu parity 검증 후 |
| `lib/registry/components/split-panel-layout/` `v2-split-panel-layout/` `split-panel-layout2/` | leftPanel / rightPanel master-detail semantics 가 canonical `table.displayMode='split'` 과 직접 대응 안 됨 (`InvyoneStudio` drag/drop, selection sync, nested child 처리 다름). palette hidden | canonical table 의 split 모드가 leftPanel/rightPanel drop / resize / selection sync / nested child 전부 구현 후 삭제 |
| `lib/registry/components/v2-tabs-widget/` `tabs/` | 옛 저장 layout alias → canonical `container` (containerType="tabs"). palette hidden | DB 상 `v2-tabs-widget` 사용 화면 0 건 확인 후 |
| `lib/registry/components/v2-section-card/` `v2-section-paper/` `section-card/` `section-paper/` | alias 로 canonical `container` (containerType="section", sectionVariant) 로 라우팅. palette hidden | canonical container 의 sectionVariant 가 `card` / `paper` parity 검증 후 |
| `lib/registry/components/v2-repeat-container/` `repeat-container/` | canonical `container.containerType="repeater"` skeleton 만 있고 데이터 lookup / 선택 / append 인프라 부족 | canonical container 의 repeater 모드가 dataSourceType / 선택 / append / 인라인 add 완성 후 |
| `lib/registry/components/v2-repeater/` | `basicV2Components` 에 별도 노출 (데이터 조회/선택 special palette item) | canonical container repeater + canonical table multi-select 가 동일 UX 구현 후 |
| `lib/registry/components/accordion-basic/` | alias 로 canonical `container` 라우팅 (다만 container.containerType="accordion" skeleton 만 존재). palette hidden | canonical container accordion 모드 완성 후 |
| `lib/registry/components/conditional-container/` | palette hidden. 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 부족 | 도메인별 별도 마이그레이션 필요 |
| `lib/schemas/componentConfig.ts` 의 v2-* schema/default | 옛 저장 layout load / save 시 schema validation 용 migration adapter (Goal §4.5) | 옛 저장본을 모두 canonical 로 재저장 후 |
| `lib/utils/layoutV2Converter.ts` (`v2-tabs-widget` / `v2-split-panel-layout` nested defaults) | nested tabs / split layout 의 옛 저장본 변환용 | 옛 layout 데이터 마이그레이션 후 |
| `lib/utils/multilangLabelExtractor.ts` `MultilangSettingsModal.tsx` (`aggregation-widget` i18n) | 옛 i18n 데이터 처리 | i18n 도 canonical stats 기반으로 마이그레이션 시 |
| `lib/registry/hoc/withContainerQuery.css` `app/test-card-responsive/page.tsx` (`v2-aggregation-widget` / `v2-table-list` 참조) | 옛 container query CSS / 반응형 테스트 페이지 | UI 테스트 페이지 cleanup 시 |
| `InvyoneStudio.tsx` 의 `split-panel-layout` / `v2-split-panel-layout` drag/drop 분기 | 옛 화면의 leftPanel/rightPanel drop 처리 (canonical table split 으로 대체 불가) | split-panel-layout 폴더 삭제와 동시에 |
| `components/v2/config-panels/V2AggregationWidgetConfigPanel.tsx` `V2StatusCountConfigPanel.tsx` `V2TableListConfigPanel.tsx` `V2SplitPanelLayoutConfigPanel.tsx` `V2RepeatContainerConfigPanel.tsx` | `getComponentConfigPanel` 직접 import 로 옛 ID 사용 시 노출. canonical 이 우선 사용됨 | 옛 ID 가 0 건이 되면 삭제 |
| `types/v2-components.ts` `types/component-events.ts` `types/screen-management.ts` 의 옛 컴포넌트 type 정의 | 옛 컴포넌트 type signature 유지 | 옛 ID 완전 제거 후 |

---

## 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 건 — 전부 §4 분류 (alias 라우팅 / hidden / migration adapter / i18n / 테스트 페이지) |
| `rg "v2-table-list\|table-list\|v2-split-panel-layout\|split-panel-layout\|split-panel-layout2" frontend/lib frontend/components frontend/app frontend/types` | 225 건 — 전부 §4 분류 (FlowWidget shared / split master-detail / 옛 type 정의 / alias) |
| `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 건 — 전부 §4 분류 (alias / canonical container 인식 추가됨 / domain 보존 / 옛 layout converter) |
| `cd backend-spring && ./gradlew compileJava` | **BUILD SUCCESSFUL** (UP-TO-DATE, backend 변경 0 건) |
| FieldConfig / DataPort 계약 | `frontend/types/invyone-component.ts` 변경 0 건. canonical stats / table / chart / card-list / grouped-table 의 `dataPorts` 선언 그대로 |

### 5.1 Codex follow-up verification

Claude Goal 종료 후 Codex가 독립 검증하면서 data transfer 후보 필터의
false-positive 위험을 보정했다.

- 기존 변경은 `["table", "table-list", ...].some((t) => type.includes(t))`
  형태였고, `table-search-widget` 같은 검색 컴포넌트도 `"table"` substring
  때문에 후보로 잡힐 수 있었다.
- `DataTab.tsx`, `ActionTab.tsx`, `InvLegacyButtonConfigPanel.tsx` 에
  `isDataTransferComponentType()` helper 를 추가했다.
- canonical `"table"` 은 exact match 로만 허용하고, legacy 호환 문자열
  (`table-list`, `repeater-field-group`, `form-group`, `data-table`) 만 substring
  match 를 유지한다.

추가 확인:

| Command | Result |
|---|---|
| `git diff --check` | pass |
| input forbidden rg | 0 건 |
| EntityPicker forbidden rg | 0 건 |
| `cd backend-spring && ./gradlew compileJava` | BUILD SUCCESSFUL |
| `npm run lint` | fail — repo 전역 기존 prettier / any / hook rule 이슈 대량 존재. 본 cleanup acceptance 에는 미포함 |
| targeted `npx eslint` on changed frontend files | fail — `InteractiveScreenViewer.tsx` 등 기존 lint 오류가 포함되어 있음. 신규 compile/blocker 는 확인되지 않음 |

---

## 6. Remaining risks

1. **canonical container repeater / accordion / conditional skeleton**:
   현재 `containerType: "tabs"` / `"section"` 외에는 미구현. 새 화면에서
   container 를 만들고 `containerType="repeater"` 로 변경해도 동작 부족.
   palette 에 container 는 한 가지로 노출되므로 사용자가 혼동 가능.
   → Phase G.x 추가 작업 필요.

2. **canonical container default config**: `containerType: "section"` 으로 시작.
   tabs 가 더 자주 사용되는 패턴이라면 default 재검토 필요.

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

4. **v2-aggregation-widget config → canonical StatsConfig 변환 어댑터 없음**:
   옛 저장 화면의 `v2-aggregation-widget` 은 alias 로 canonical stats Renderer
   로 라우팅되지만 config 형식이 다르므로 빈 stats 렌더링 가능.
   메모리 `project_solution_definition_phase` 에 따라 round-trip 호환 미보장.

5. **`getComponentConfigPanel.tsx` 의 `LEGACY_PANELS`** 에 `chart` / `stats` /
   `chart-basic` 포함. `DynamicComponentConfigPanel` 의 fallback 분기에서만
   사용되고 V2PropertiesPanel 은 `ComponentRegistry.getComponent` 를 우선
   사용하므로 실제 동작에는 영향 없음. 다만 redundant 코드.

6. **InvyoneStudio.tsx 8000+ 줄**: 9 곳 canonical container 인식 추가 완료.
   추가 위치 (drag preview, copy/paste, undo/redo 등) 가 발견되면 동일 패턴으로
   확장 필요.

---

## 7. Acceptance check 요약 (Goal 의 /goal 조건)

| 조건 | 결과 |
|---|---|
| new Studio creation paths use canonical stats / table / container / chart / card-list / grouped-table | ✅ |
| FieldConfig / DataPort contracts are preserved | ✅ (변경 0 건) |
| v2-input / v2-select / V2InputRenderer / V2SelectRenderer 재도입 X | ✅ (0 건) |
| EntityPicker / EntitySearchModal 재도입 X | ✅ (0 건) |
| `git diff --check` passes | ✅ |
| `cd backend-spring && ./gradlew compileJava` passes | ✅ (BUILD SUCCESSFUL) |
| input forbidden rg checks are 0 | ✅ |
| 남은 stats / table / container legacy/V2 grep matches 가 제거되거나 명시적으로 migration / domain / shared adapters 로 분류 | ✅ (본 보고서 §4) |
