# 2026-05-18 Canonical Data View Cleanup - Claude Goal Prompt

작성 목적: Claude Goal 모드에 그대로 넣어서 `stats`, `table`, `container`, `chart`, `card-list`, `grouped-table` 계열을 input canonical 작업 수준으로 정리하게 만들기 위한 작업 계약서다.

핵심 결론:
- `input` canonical 작업은 완료된 것으로 본다. `v2-input`, `v2-select`, old 6 input, radio/toggle/file/media 계열은 다시 만들거나 되살리지 않는다.
- 다음 정리 대상은 통계, 테이블, 컨테이너/탭, 그리고 데이터 뷰 컴포넌트다.
- 현재 `stats`, `table`, `container`, `chart`, `card-list`, `grouped-table`은 canonical 구현이 존재하지만, registry, config panel, schema, Studio drag/drop, preview, action/dataflow 경로에 legacy/V2 이름이 많이 남아 있다.
- 이번 Goal의 목적은 단순 폴더 삭제가 아니다. `FieldConfig`와 `DataPort` 계약을 보존하면서 새 화면 생성 경로를 canonical ID로 수렴시키고, old/V2 경로는 흡수 완료, migration adapter, domain/special 보존 중 하나로 명확히 분류하는 것이다.

---

## 1. 현재 확정된 선행 상태

### 1.1 Input canonical은 종료 상태

근거 문서:
- `notes/gbpark/2026-05-08-input-canonical-migration.md`
- `notes/gbpark/2026-05-12-codex-handoff-input-canonical.md`

완료 요약:
- `frontend/lib/registry/components/v2-input/` 삭제 완료
- `frontend/lib/registry/components/v2-select/` 삭제 완료
- `frontend/components/v2/V2Input.tsx` 삭제 완료
- `frontend/components/v2/V2Select.tsx` 삭제 완료
- old 6 input 폴더 삭제 완료
- `radio-basic`, `toggle-switch`, `slider-basic`, old file/media/image widget 계열 삭제 또는 shared shim화 완료
- `input`이 text, number, date, datetime, time, daterange, textarea, select, entity code-name, category, checkbox, mask, slider, color, file/image를 담당

현재 코드 기준 검증:
```bash
rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" \
  frontend/lib frontend/components frontend/app frontend/types frontend/styles

rg "EntityPicker|entity-picker|EntitySearchModal" \
  frontend/lib/registry/components/input \
  frontend/components/v2/config-panels/InvFieldConfigPanel.tsx
```

기대:
- 둘 다 코드 경로에서는 0건.
- `notes/` 문서 안의 옛 계획 문구는 검색될 수 있다. 코드 cleanup 판단에는 포함하지 않는다.

절대 금지:
- `v2-input`, `v2-select`, `V2InputRenderer`, `V2SelectRenderer` 재도입
- canonical `input` 안에 `EntityPicker` 또는 `EntitySearchModal` 재도입
- DB layout JSON을 SQL로 일괄 마이그레이션하는 방향

---

## 2. 반드시 보존할 아키텍처 계약

원천 파일: `frontend/types/invyone-component.ts`

### 2.1 FieldConfig

`FieldConfig`가 유일한 필드 규격이다. table column, form input, search filter가 모두 이 규격 위로 수렴해야 한다.

핵심 타입:
```ts
export type FieldType =
  | 'text'
  | 'number'
  | 'date'
  | 'datetime'
  | 'time'
  | 'daterange'
  | 'select'
  | 'entity'
  | 'checkbox'
  | 'textarea'
  | 'file'
  | 'code';
```

주의:
- `entity`는 검색 모달이 아니라 참조 테이블 code-name 옵션 source다.
- 명시적인 검색 모달 UI는 `entity-search-input` 같은 explicit 컴포넌트 영역이며 canonical `input`으로 되돌려 넣지 않는다.
- `FieldConfig` 자체를 늘려서 특정 업무 도메인을 해결하지 않는다. 컴포넌트별 표시/동작 옵션은 각 component config로 둔다.

### 2.2 DataPort

`DataPort`와 `Connection` 계약은 줄이지 않는다.

핵심 타입:
```ts
export type DataPortType = 'row' | 'rows' | 'value' | 'params';

export type ReservedDataPortName =
  | 'searchParams'
  | 'refreshTrigger'
  | 'selectedRow'
  | 'selectedRows'
  | 'loadRow'
  | 'formData'
  | 'savedRow'
  | 'clicked';
```

현재 런타임 상태:
- `frontend/lib/dataPort/DataPortBus.ts`
- `frontend/lib/dataPort/runtime.ts`
- `setupConnections(connections, bus)`는 존재한다.
- 그러나 현재 canonical 컴포넌트들은 대부분 `DataPortBus` 직접 publish/subscribe보다 props/callback 방식으로 연결되어 있다.

작업 원칙:
- `dataPorts` 선언을 삭제하거나 축소하지 않는다.
- Goal 작업 중 DataPortBus 전체 wiring을 완성하지 못하더라도 계약은 유지한다.
- `table`의 `selectedRow/selectedRows`, `searchParams/refreshTrigger` 이름은 유지한다.
- `stats`, `chart`, `card-list`, `grouped-table`의 `data(rows)` input 선언도 유지한다.
- DataPort wiring을 손대는 경우에는 별도 작은 단계로 하고, 기존 props/callback 흐름을 깨지 않는다.

---

## 3. 현재 canonical 컴포넌트 상태

### 3.1 `stats`

주요 파일:
- `frontend/lib/registry/components/stats/index.ts`
- `frontend/lib/registry/components/stats/types.ts`
- `frontend/lib/registry/components/stats/StatsComponent.tsx`
- `frontend/lib/registry/components/stats/use-stats-data.ts`
- `frontend/lib/registry/components/stats/InvStatsConfigPanel.tsx`

현재 상태:
- canonical id는 `stats`.
- default config는 DB-first다. item에 `value: 0` 같은 mock 값을 박지 않고 `dataSource: { aggregation: "count" }`로 시작한다.
- `StatsItem.dataSource`는 `tableName`, `aggregation`, `columnName`, `filters`를 가진다.
- `filters`는 canonical input의 `OptionFilter`를 재사용한다.
- `useStatsData`는 `/table-management/tables/{tableName}/aggregate` 호출로 값을 가져온다.
- 디자인 모드에서도 preview fetch를 한다. 다만 350ms debounce를 둔다.
- `field`/`user` filter value는 runtime에서 `formData`/`userContext`로 치환한다.
- 실패는 카드 단위 error로 처리해야 한다.

관련 API:
- `frontend/lib/api/stats.ts`
- `backend-spring/src/main/java/com/erp/controller/TableManagementController.java`
- `backend-spring/src/main/java/com/erp/service/TableManagementService.java`

지원 backend endpoint:
```text
POST /api/table-management/tables/{tableName}/aggregate
POST /api/table-management/tables/{tableName}/aggregate-group
POST /api/table-management/tables/{tableName}/select-rows
```

남은 문제:
- legacy/V2 stats 컴포넌트 폴더가 아직 등록된다.
- `v2-aggregation-widget`, `aggregation-widget`, `v2-status-count`가 renderer/config/schema/alias 경로에 남아 있다.
- `v2-status-count`의 `relationColumn`, `parentColumn` 같은 관계형 상태 카운트 semantics가 canonical `stats`의 `filters`로 완전히 표현 가능한지 검증이 필요하다.

삭제 후보:
- `frontend/lib/registry/components/aggregation-widget/`
- `frontend/lib/registry/components/v2-aggregation-widget/`
- `frontend/lib/registry/components/v2-status-count/`
- 단, 실제 import와 저장 화면 compatibility 경로를 모두 확인한 뒤 삭제한다.

보존 가능 조건:
- `v2-status-count`의 관계 필터를 canonical `StatsItem.dataSource.filters`로 안전하게 매핑할 수 없으면 삭제하지 말고 read-only migration adapter 또는 explicit preserved legacy로 분류한다.
- adapter를 남길 경우 새 생성 경로와 palette에서는 반드시 제외한다.

### 3.2 `table`

주요 파일:
- `frontend/lib/registry/components/table/index.ts`
- `frontend/lib/registry/components/table/types.ts`
- `frontend/lib/registry/components/table/TableComponent.tsx`
- `frontend/lib/registry/components/table/useTableData.ts`
- `frontend/lib/registry/components/table/InvTableConfigPanel.tsx`

현재 상태:
- canonical id는 `table`.
- display mode는 `table`, `split`, `grouped`, `pivot`, `card`.
- `TableDefinition.dataPorts`는 다음을 선언한다.
```ts
inputs: [
  { name: "searchParams", type: "params" },
  { name: "refreshTrigger", type: "value" },
],
outputs: [
  { name: "selectedRow", type: "row" },
  { name: "selectedRows", type: "rows" },
],
```
- `useTableData`는 `entityJoinApi.getTableDataWithJoins()`를 사용한다.
- canonical `TableComponent`는 `searchParams` props를 외부 검색 조건으로 받고, 선택 상태는 `onRowSelect`, `onSelectedRowsChange` 같은 callback으로 올린다.
- DataPortBus publish/subscribe는 아직 직접 연결되어 있지 않다.

남은 문제:
- old `table-list`와 `v2-table-list`가 여전히 크고 기능이 많다.
- `FlowWidget`가 `table-list/SingleTableWithSticky`와 `table-list/types`의 `ColumnConfig`를 직접 import한다.
- `v2-button-primary`, `ActionTab`, `DataTab`, `InvLegacyButtonConfigPanel`, `TabsWidget`, `RealtimePreviewDynamic`, `InvyoneStudio` 등에 `table-list`, `split-panel-layout` 탐색 로직이 남아 있다.
- `componentConfig.ts`에 `v2-table-list`, `v2-split-panel-layout` schema/default가 남아 있다.
- `layoutV2Converter.ts`가 nested `v2-split-panel-layout`에 특수 기본값을 적용한다.
- `responsiveDefaults.ts`는 `table-list`만 full width로 본다. canonical `table`, `grouped-table`, `card-list`, 필요 시 `chart`까지 재검토해야 한다.

삭제 후보:
- `frontend/lib/registry/components/table-list/`
- `frontend/lib/registry/components/v2-table-list/`
- `frontend/lib/registry/components/split-panel-layout/`
- `frontend/lib/registry/components/v2-split-panel-layout/`
- `frontend/lib/registry/components/split-panel-layout2/`

단, 삭제 전에 반드시 분류:
- `FlowWidget`의 `SingleTableWithSticky` 의존은 shared internal로 추출하거나, FlowWidget 전용 local component로 복사/이관하거나, 해당 old folder를 explicit shared dependency로 보존한다.
- `modal-repeater-table`, `simple-repeater-table`, `tax-invoice-list`는 table 흡수 대상 문서에 들어 있지만 업무 특화 기능이 많다. 이번 Goal에서 parity가 없으면 삭제하지 말고 explicit/special로 분류한다.
- `split-panel-layout` 계열은 canonical `table displayMode="split"`로 완전히 흡수 가능한지 확인한다. master-detail, resize, selection sync, nested child behavior가 빠져 있으면 adapter 보존이 필요하다.

### 3.3 `container`

주요 파일:
- `frontend/lib/registry/components/container/index.ts`
- `frontend/lib/registry/components/container/types.ts`
- `frontend/lib/registry/components/container/ContainerComponent.tsx`
- `frontend/lib/registry/components/container/InvContainerConfigPanel.tsx`

현재 상태:
- canonical id는 `container`.
- `containerType`은 `tabs`, `section`, `accordion`, `repeater`, `conditional`.
- tabs 모드는 활성 탭의 `components`만 `DynamicComponentRenderer`로 렌더링한다.
- `ContainerTab.components` child shape는 `id`, `componentType`, `componentConfig`, `label`, `size` 중심이다.
- 디자인 모드에서 tab body에 drop-zone data attribute가 있다.

남은 문제:
- section/accordion/repeater/conditional은 아직 skeleton에 가깝다.
- old tabs와 V2 tabs 경로가 Studio drag/drop, selection, config panel에 남아 있다.
- `layoutV2Converter.ts`가 nested `v2-tabs-widget` 기본값을 별도 처리한다.
- `v2-repeat-container`, `repeat-container`, `v2-repeater`는 반복 데이터 입력/선택 semantics가 있어 단순 container 삭제 대상이 아니다.

삭제 후보:
- `frontend/lib/registry/components/v2-tabs-widget/`
- `frontend/lib/registry/components/tabs/`
- `frontend/lib/registry/components/v2-section-card/`
- `frontend/lib/registry/components/v2-section-paper/`
- `frontend/lib/registry/components/section-card/`
- `frontend/lib/registry/components/section-paper/`
- `frontend/lib/registry/components/accordion-basic/`
- `frontend/lib/registry/components/v2-repeat-container/`
- `frontend/lib/registry/components/repeat-container/`

단, 삭제 전에 반드시 분류:
- tabs는 canonical `containerType="tabs"`로 migration 가능해야 한다.
- section card/paper는 `containerType="section"` + `sectionVariant`로 migration 가능해야 한다.
- repeat/repeater는 canonical container skeleton만으로는 부족할 가능성이 높다. parity가 없으면 삭제 금지.

### 3.4 `chart`

주요 파일:
- `frontend/lib/registry/components/chart/index.ts`
- `frontend/lib/registry/components/chart/types.ts`
- `frontend/lib/registry/components/chart/ChartComponent.tsx`
- `frontend/lib/registry/components/chart/use-chart-data.ts`
- `frontend/lib/registry/components/chart/InvChartConfigPanel.tsx`

현재 상태:
- canonical id는 `chart`.
- `dataSource.tableName + groupBy + aggregation`으로 `/aggregate-group` 호출.
- chart type은 bar, horizontalBar, line, donut 중심.
- `dataPorts.inputs = [{ name: "data", type: "rows" }]`.

남은 문제:
- `DynamicComponentRenderer`의 `INVYONE_UNIFIED_IDS`에 `chart`가 빠져 있다.
- old placeholder `ChartRenderer.tsx` 같은 legacy display 경로와 canonical chart 경로가 혼재할 수 있다.
- `getComponentConfigPanel.tsx`에는 canonical config panel이 잡혀 있다.

### 3.5 `card-list`

주요 파일:
- `frontend/lib/registry/components/card-list/index.ts`
- `frontend/lib/registry/components/card-list/types.ts`
- `frontend/lib/registry/components/card-list/CardListComponent.tsx`
- `frontend/lib/registry/components/card-list/InvCardListConfigPanel.tsx`

현재 상태:
- canonical id는 `card-list`.
- row 목록을 카드 카탈로그처럼 표시한다.
- shared `use-table-rows`를 통해 `/select-rows` 호출.
- `dataPorts.inputs = [{ name: "data", type: "rows" }]`.

남은 문제:
- config panel의 field mapping 일부는 free text 중심이다. `ColumnPicker`로 바꾸면 좋지만, cleanup보다 우선순위는 낮다.
- `DynamicComponentRenderer`의 `INVYONE_UNIFIED_IDS`에 빠져 있다.

### 3.6 `grouped-table`

주요 파일:
- `frontend/lib/registry/components/grouped-table/index.ts`
- `frontend/lib/registry/components/grouped-table/types.ts`
- `frontend/lib/registry/components/grouped-table/GroupedTableComponent.tsx`
- `frontend/lib/registry/components/grouped-table/InvGroupedTableConfigPanel.tsx`

현재 상태:
- canonical id는 `grouped-table`.
- `/select-rows`로 받은 row를 `groupBy`로 클라이언트 그룹핑한다.
- `dataPorts.inputs = [{ name: "data", type: "rows" }]`.

남은 문제:
- config panel 일부가 free text 중심이다.
- `DynamicComponentRenderer`의 `INVYONE_UNIFIED_IDS`에 빠져 있다.
- responsive full-width 판단에 빠져 있다.

---

## 4. 현재 legacy/V2 잔여 진입점

### 4.1 Registry auto-register

파일: `frontend/lib/registry/components/index.ts`

현재 자동 등록되는 legacy/V2 예:
```text
table-list
split-panel-layout
aggregation-widget
repeat-container
tabs/tabs-component
v2-repeater
v2-split-panel-layout
v2-aggregation-widget
v2-table-list
v2-repeat-container
v2-section-card
v2-section-paper
v2-tabs-widget
v2-status-count
```

동시에 canonical도 등록된다:
```text
divider
title
button
search
input
stats
chart
card-list
grouped-table
table
container
```

정리 목표:
- 새 생성 경로와 palette에는 canonical만 남긴다.
- legacy/V2가 필요하면 runtime migration adapter 또는 explicit preserved domain/special로 분리한다.
- `components/index.ts`에서 무조건 import되는 old renderer는 최종적으로 없애거나, 보존 사유가 주석으로 분명해야 한다.

### 4.2 Dynamic renderer alias

파일: `frontend/lib/registry/DynamicComponentRenderer.tsx`

현재 `INVYONE_UNIFIED_IDS`:
```text
divider, title, button, search, input, stats, table, container
```

빠진 canonical:
```text
chart, card-list, grouped-table
```

현재 alias:
```text
v2-aggregation-widget -> stats
aggregation-widget -> stats
v2-status-count -> stats
v2-table-list -> table
table-list -> table
v2-tabs-widget -> container
v2-section-card -> container
v2-section-paper -> container
v2-repeat-container -> container
section-card -> container
section-paper -> container
accordion-basic -> container
```

정리 목표:
- canonical set에 `chart`, `card-list`, `grouped-table`을 포함한다.
- alias는 삭제 대상이 아니라 migration boundary다. old 저장 화면을 지원할지, 새 솔루션 기준으로 끊을지 정책을 파일 단위로 결정해야 한다.
- input 때처럼 완전 제거 가능한 V2와 달리 table/container는 기능 차이가 커서 alias를 한 번에 날리면 위험하다.

### 4.3 Config panel loader

파일: `frontend/lib/utils/getComponentConfigPanel.tsx`

canonical map:
```text
divider, title, button, search, input, stats, table, container,
chart, card-list, grouped-table
```

아직 직접 map에 남은 legacy/V2:
```text
table-list
v2-table-list
tabs-widget
v2-tabs-widget
tabs
v2-tabs
aggregation-widget
v2-aggregation-widget
section-card
v2-section-card
section-paper
v2-section-paper
split-panel-layout
v2-split-panel-layout
repeat-container
v2-repeat-container
```

정리 목표:
- alias로 canonical panel을 보여주는 것은 migration boundary로 허용 가능.
- 그러나 새 생성되지 않을 old id의 직접 old config panel import는 제거 후보다.
- 제거 전에는 old config schema가 canonical config로 변환 가능한지 확인한다.

### 4.4 Template migration

파일: `frontend/lib/utils/templateMigrate.ts`

현재 legacy id를 unified id로 정규화한다.

정리 목표:
- 이 파일은 DB layout SQL migration이 아니라 runtime/view conversion boundary다.
- 무작정 삭제하지 않는다.
- old id를 canonical id로 바꾸는 마지막 방어선으로 둘지, 새 솔루션 기준으로 축소할지 명확히 한다.

### 4.5 Layout V2 converter와 schema/default

파일:
- `frontend/lib/schemas/componentConfig.ts`
- `frontend/lib/utils/layoutV2Converter.ts`

남은 schema/default 예:
```text
v2-table-list
v2-split-panel-layout
v2-repeat-container
v2-aggregation-widget
v2-tabs-widget
v2-repeater
```

정리 목표:
- old 컴포넌트가 더 이상 생성/로드되지 않는다면 schema/default 제거.
- old 저장 layout을 runtime에서 읽어야 한다면 schema/default를 migration adapter로 둘 수 있다.
- 삭제할 때는 `getDefaultsByUrl`, `convertV2ToLegacy`, nested tabs/split handling이 깨지지 않는지 확인한다.

### 4.6 Studio drag/drop and selection

주요 파일:
- `frontend/components/screen/InvyoneStudio.tsx`
- `frontend/components/screen/panels/ComponentsPanel.tsx`
- `frontend/components/screen/RealtimePreviewDynamic.tsx`
- `frontend/components/screen/InteractiveScreenViewer.tsx`
- `frontend/components/screen/config-panels/button-config/ActionTab.tsx`
- `frontend/components/screen/config-panels/button/DataTab.tsx`

현재 잔여 문자열:
```text
table-list
v2-table-list
split-panel-layout
v2-split-panel-layout
tabs-widget
v2-tabs-widget
aggregation-widget
v2-aggregation-widget
v2-status-count
```

정리 목표:
- 새 drag 생성은 canonical id로만 만든다.
- old id 조건문은 삭제, canonical 추가, 또는 migration adapter로 분류한다.
- tab 내부 drop은 `containerType="tabs"`의 `tabs[].components` shape를 기준으로 한다.
- split panel 내부 drop/selection은 canonical `table displayMode="split"`로 흡수 가능한지 먼저 확인한다.

---

## 5. 목표 end state

새 화면 생성 기준 basic palette:
```text
input
search
button
title
divider
stats
table
container
chart
card-list
grouped-table
```

허용되는 advanced/domain:
```text
v2-bom-tree
v2-bom-item-editor
v2-approval-step
map
v2-shipping-plan-editor
v2-timeline-scheduler
v2-rack-structure
v2-process-work-standard
v2-item-routing
```

조건부 보존:
```text
v2-repeater
modal-repeater-table
simple-repeater-table
tax-invoice-list
universal-form-modal
entity-search-input
autocomplete-search-input
selected-items-detail-input
mail-recipient-selector
category-manager
location-swap-selector
related-data-buttons
flow-widget
```

보존 조건:
- business/domain/special 컴포넌트로서 canonical table/container와 기능이 다르다.
- 또는 다른 기능이 직접 import하는 shared internal을 포함한다.
- 또는 old 저장 화면 runtime adapter로만 필요하다.

보존 시 규칙:
- palette 새 생성 경로에서 숨긴다.
- `components/index.ts`에서 자동 등록할지 여부를 재검토한다.
- 보존 이유와 삭제 불가 조건을 최종 보고서에 쓴다.

---

## 6. Claude Goal 실행 프롬프트

아래 블록을 Claude Goal에 그대로 넣는다.

```text
You are working in /Users/gbpark/invyone.

Goal:
Clean up the INVYONE Studio data-view component system after canonical input migration. Bring stats/table/container/chart/card-list/grouped-table to the same architectural standard as canonical input: new creation paths must use canonical IDs, V2/legacy paths must be absorbed, removed, or explicitly preserved as migration/domain adapters, and FieldConfig/DataPort contracts must be preserved.

Read this whole file first:
notes/gbpark/2026-05-18-canonical-data-view-goal.md

Non-negotiable rules:
1. Do not reintroduce v2-input, v2-select, V2InputRenderer, V2SelectRenderer, V2Input.tsx, or V2Select.tsx.
2. Do not put EntityPicker or EntitySearchModal into canonical input. canonical input entity mode is code-name option source only.
3. Do not write DB layout JSON migration SQL. This is new-solution cleanup, not DB data rewrite.
4. Do not shrink FieldConfig or DataPort contracts in frontend/types/invyone-component.ts.
5. Do not delete a legacy/V2 folder just because it has an alias. First prove it has no active imports, or classify it as preserved with a reason.
6. Do not delete domain/special components unless you have implemented equivalent canonical behavior and verified all active references.
7. Prefer the existing codebase patterns: ComponentRegistry, createComponentDefinition, CP panel primitives, ColumnPicker, OptionFilterRow, useDbTables, useStatsData, use-table-rows, aggregate/select backend APIs.
8. Avoid broad rewrites. Work in small phases, run grep after each phase, and keep a final report.

First, run inventory commands and write down what each match means:

rg -n "v2-aggregation-widget|v2-status-count|aggregation-widget" \
  frontend/lib frontend/components frontend/app frontend/types

rg -n "v2-table-list|table-list|v2-split-panel-layout|split-panel-layout|split-panel-layout2" \
  frontend/lib frontend/components frontend/app frontend/types

rg -n "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

rg -n "INVYONE_UNIFIED_IDS|LEGACY_TO_UNIFIED|CONFIG_PANEL_ALIAS|v2-table-list|v2-aggregation-widget|v2-status-count|v2-tabs-widget|v2-split-panel-layout" \
  frontend/lib/registry frontend/lib/utils frontend/lib/schemas frontend/components/screen

Do not stop after inventory. Implement the cleanup in phases below.

Phase 1 - canonical renderer/config entry cleanup:
- Add chart, card-list, grouped-table to canonical/unified routing where needed, especially DynamicComponentRenderer INVYONE_UNIFIED_IDS.
- Ensure ComponentsPanel basic IDs contain only intended canonical basics plus explicitly allowed advanced/domain entries.
- Ensure responsive defaults recognize canonical table/grouped-table/card-list and any other true full-width data view.
- Keep DataPort declarations intact.
- Run grep to confirm no new v2-input/v2-select references.

Phase 2 - stats cleanup:
- Treat stats as the canonical replacement for v2-aggregation-widget, aggregation-widget, and v2-status-count.
- Check v2-aggregation-widget and aggregation-widget config shapes. Map their items/table/filter concepts into StatsConfig.items[].dataSource where safe.
- Check v2-status-count config. If statusColumn/items/tableName can map to StatsItem.dataSource.filters, implement the adapter. If relationColumn/parentColumn semantics cannot be safely mapped, preserve v2-status-count as a read-only legacy/domain adapter and document why.
- Remove old direct config panel imports and registry auto-register imports only after proving there are no active code imports that need the old implementation.
- Remove old stats folders only if rg proves no active imports remain and runtime alias/migration no longer needs the implementation.
- Final acceptable state for stats:
  - New creation uses id "stats".
  - Palette does not expose v2-aggregation-widget, aggregation-widget, or v2-status-count.
  - Old ids either route to canonical stats or are explicitly preserved with reason.

Phase 3 - table cleanup:
- Treat table as the canonical replacement for table-list and v2-table-list when behavior is equivalent.
- Compare old table-list/v2-table-list features against canonical table before deleting: selection, multi-select, sort, pagination, column visibility/order, entity label display, export, action buttons, context menus, card mode.
- FlowWidget imports SingleTableWithSticky from table-list. Do not break it. Either extract that shared table view into a neutral shared location, migrate FlowWidget to canonical table internals, or preserve table-list as explicit shared dependency with a written reason.
- Update ActionTab, DataTab, ButtonPrimaryComponent, InvLegacyButtonConfigPanel, RealtimePreviewDynamic, InvyoneStudio, ScreenNode, TabsWidget references so they recognize canonical table, not only table-list.
- For split-panel-layout/v2-split-panel-layout, verify whether canonical table displayMode="split" has equivalent master-detail behavior. If not equivalent, preserve split-panel legacy with reason. If equivalent, migrate creation/config paths to canonical table split mode.
- Do not delete modal-repeater-table, simple-repeater-table, tax-invoice-list, universal-form-modal table sections unless parity is implemented and all imports are updated. These are likely special/business components.
- Final acceptable state for table:
  - New creation uses id "table".
  - New table config panel is InvTableConfigPanel.
  - searchParams and selectedRows/selectedRow flows still work through existing props/callbacks and DataPort declarations remain.
  - Remaining table-list/split-panel strings are either migration aliases or explicitly documented preserved dependencies.

Phase 4 - container/tabs cleanup:
- Treat container as canonical for tabs, section, accordion, repeat, and conditional only where behavior exists.
- For v2-tabs-widget/tabs-widget, migrate creation and drop/selection paths to container with containerType="tabs".
- Ensure tab child shape uses ContainerTab.components with child.componentType and child.componentConfig.
- Update InvyoneStudio tab drop/selection logic to support canonical container as the primary path.
- For section-card/section-paper/v2-section-card/v2-section-paper, map to containerType="section" and sectionVariant card/paper where behavior is equivalent.
- For accordion/repeat/conditional, do not delete old components if canonical container still only has skeleton behavior. Preserve with reason or implement missing behavior first.
- For v2-repeat-container and v2-repeater, verify data lookup/selection/repeater semantics. Do not delete unless canonical repeater behavior is complete.
- Final acceptable state for container:
  - New tabs are canonical container tabs.
  - Old tabs ids are not palette-created.
  - Remaining old container ids are classified.

Phase 5 - chart/card-list/grouped-table cleanup:
- Ensure chart, card-list, grouped-table are first-class canonical components everywhere a canonical component ID list exists.
- Ensure their config panels are reachable through getComponentConfigPanel.
- Ensure they are visible in the intended palette group.
- Ensure they keep dataPorts input data(rows).
- Do not over-scope into full UX redesign. ColumnPicker improvements are allowed only if low-risk.

Phase 6 - schema/migration/alias cleanup:
- Review and update these files together:
  - frontend/lib/registry/components/index.ts
  - frontend/lib/registry/DynamicComponentRenderer.tsx
  - frontend/lib/utils/getComponentConfigPanel.tsx
  - frontend/lib/utils/templateMigrate.ts
  - frontend/lib/schemas/componentConfig.ts
  - frontend/lib/utils/layoutV2Converter.ts
  - frontend/types/v2-components.ts
  - frontend/lib/utils/responsiveDefaults.ts
  - frontend/components/screen/panels/ComponentsPanel.tsx
  - frontend/components/screen/InvyoneStudio.tsx
  - frontend/components/screen/RealtimePreviewDynamic.tsx
  - frontend/components/screen/config-panels/button-config/ActionTab.tsx
  - frontend/components/screen/config-panels/button/DataTab.tsx
  - frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx
- Remove direct old renderer/config/schema imports only when the old implementation is no longer needed.
- Keep migration alias only if old saved layouts still need it, and document it as migration-only.

Phase 7 - verification:
Run:

git diff --check

rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" \
  frontend/lib frontend/components frontend/app frontend/types frontend/styles

rg "EntityPicker|entity-picker|EntitySearchModal" \
  frontend/lib/registry/components/input \
  frontend/components/v2/config-panels/InvFieldConfigPanel.tsx

rg -n "v2-aggregation-widget|v2-status-count|aggregation-widget" \
  frontend/lib frontend/components frontend/app frontend/types

rg -n "v2-table-list|table-list|v2-split-panel-layout|split-panel-layout|split-panel-layout2" \
  frontend/lib frontend/components frontend/app frontend/types

rg -n "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

cd backend-spring && ./gradlew compileJava

Also run the repo's frontend type/lint command if it exists. If whole-project frontend typecheck fails due pre-existing errors, capture the errors and prove changed files did not introduce new module-not-found or type errors.

Manual runtime smoke scenarios to preserve:
1. Add a new stats component, configure user_info count, and configure another item with status="재직" count.
2. Add a new table component, load columns from a real table, select one row, then multi-select rows if enabled.
3. Add a new search component and pass its searchParams to the table through the existing supported flow.
4. Add a container tabs component, create two tabs, drop a stats child into one tab and a table child into another tab, switch tabs.
5. Add a chart grouped by a column.
6. Add card-list and grouped-table with tableName, selected fields, groupBy/orderBy where relevant.

Final report format:
- Summary of canonical entry points changed.
- Files changed.
- Deleted folders/files.
- Legacy/V2 components still preserved and exact reason for each.
- Verification commands and results.
- Remaining risks or explicit next phases.
```

---

## 7. 작업 세부 설계

### 7.1 Stats 설계

Target config:
```ts
type StatsConfig = {
  orientation?: "horizontal" | "vertical" | "grid";
  style?: "card" | "chip" | "bigNumber";
  columns?: number;
  title?: string;
  sourceTable?: string;
  items?: Array<{
    id?: string;
    label: string;
    value?: string | number;
    icon?: string;
    color?: string;
    format?: string;
    delta?: string;
    deltaDirection?: "up" | "down" | "neutral";
    dataSource?: {
      tableName?: string;
      aggregation?: "count" | "sum" | "avg" | "min" | "max" | "distinctCount";
      columnName?: string;
      filters?: OptionFilter[];
    };
  }>;
};
```

Legacy mapping draft:

| Legacy source | Canonical stats mapping |
|---|---|
| `v2-aggregation-widget.items[]` | `StatsConfig.items[]` |
| item label/title/name | `StatsItem.label` |
| item icon/color | `StatsItem.icon/color` |
| tableName/sourceTable | `StatsItem.dataSource.tableName` or config-level sourceTable fallback |
| aggregation/count/sum/avg | `StatsItem.dataSource.aggregation` |
| column/field | `StatsItem.dataSource.columnName` |
| filters | `StatsItem.dataSource.filters` |
| static value | `StatsItem.value` fallback only |
| `v2-status-count.items[]` | one stat item per status |
| status item value/code | filter `{ column: statusColumn, operator: "=", value }` |
| relationColumn/parentColumn | only map if field/user filter can represent it safely |

Stop condition:
- If a status-count feature depends on parent selected row or relation context that canonical stats cannot currently receive, preserve `v2-status-count` and document it.

### 7.2 Table 설계

Target config:
```ts
type TableConfig = {
  displayMode?: "table" | "split" | "grouped" | "pivot" | "card";
  tableName?: string;
  columns?: TableColumn[];
  fields?: FieldConfig[];
  selectionMode?: "none" | "single" | "multiple";
  showCheckbox?: boolean;
  pagination?: { enabled?: boolean; pageSize?: number };
  defaultSort?: { column: string; direction: "asc" | "desc" };
  grouped?: { groupBy?: string };
  pivot?: PivotGridProps;
  card?: TableCardColumnMapping & TableCardStyleConfig;
};
```

Old table parity checklist:
- tableName/source table load
- server pagination
- sorting
- row selection
- multi selection
- checkbox column
- selectedRows callback
- entity label display
- column order and width
- hidden/visible columns
- card mode
- grouped mode
- pivot mode
- export/download
- row action buttons
- open modal with selected row
- split/master-detail mode
- context menu

Delete only after:
```bash
rg -n "from \"@/lib/registry/components/table-list|from '@/lib/registry/components/table-list|components/table-list|table-list/" frontend
rg -n "from \"@/lib/registry/components/v2-table-list|components/v2-table-list|v2-table-list/" frontend
```

If references remain:
- migrate them to canonical/shared code, or
- preserve the old folder as shared/legacy dependency with explicit comment.

### 7.3 Container 설계

Target config:
```ts
type ContainerConfig = {
  containerType?: "tabs" | "section" | "accordion" | "repeater" | "conditional";
  title?: string;
  tabs?: Array<{
    id: string;
    label: string;
    icon?: string;
    components?: Array<{
      id: string;
      componentType: string;
      componentConfig?: Record<string, any>;
      label?: string;
      size?: { width?: number | string; height?: number | string };
    }>;
    dataSources?: unknown[];
    filters?: unknown[];
    viewType?: string;
  }>;
  defaultTab?: string;
  sectionVariant?: "card" | "paper" | "plain";
  collapsible?: boolean;
  defaultCollapsed?: boolean;
};
```

Tabs mapping:

| Old tabs | Canonical container |
|---|---|
| `v2-tabs-widget` | `container` |
| `tabs-widget` | `container` |
| `config.tabs[]` | `componentConfig.tabs[]` |
| tab child components | `tab.components[]` |
| defaultTab/defaultTabId | `defaultTab` |

Stop condition:
- If old tabs support nested absolute positioning that canonical tabs does not, keep adapter or implement equivalent before deletion.

### 7.4 Chart/card-list/grouped-table 설계

These are already canonical view components. Treat them as first-class, not as old dashboard placeholders.

Required paths:
- `ComponentRegistry` registration
- `getComponentConfigPanel`
- `ComponentsPanel` visibility
- `DynamicComponentRenderer` canonical ID set
- responsive default if the component is naturally wide

Do not confuse:
- admin dashboard `chart` widgets
- POP dashboard chart
- canonical Studio `frontend/lib/registry/components/chart`

---

## 8. 위험 구간

### 8.1 `table-list` is not just old UI

`FlowWidget` imports:
```text
frontend/components/screen/widgets/FlowWidget.tsx
  -> frontend/lib/registry/components/table-list/SingleTableWithSticky
  -> frontend/lib/registry/components/table-list/types
```

So deleting `table-list/` blindly will break FlowWidget.

### 8.2 Button action config searches old table ids

Files:
```text
frontend/components/screen/config-panels/button-config/ActionTab.tsx
frontend/components/screen/config-panels/button/DataTab.tsx
frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx
frontend/components/v2/config-panels/InvLegacyButtonConfigPanel.tsx
```

They still search for:
```text
table-list
data-table
repeater-field-group
split-panel-layout
screen-split-panel
```

Need to add canonical `table` support and avoid breaking selectedRows behavior.

### 8.3 `container` is not fully equivalent to all old container components

Current canonical tabs render children. Other modes are not necessarily feature-complete.

Do not delete:
- `repeat-container`
- `v2-repeat-container`
- `v2-repeater`
- `conditional-container`
- `accordion-basic`

unless behavior is implemented or no active use remains.

### 8.4 `componentConfig.ts` and `layoutV2Converter.ts` are load/save boundaries

Removing schema/default entries can break layout load/save even if renderer imports are gone.

Before deleting schema/default:
- inspect `getDefaultsByUrl`
- inspect `convertV2ToLegacy`
- inspect nested tabs/split default application
- run smoke load of a layout if possible

### 8.5 Full-project frontend typecheck may already fail

Existing known failures have appeared historically around unrelated module aliases and camelCase/snake_case drift.

If full `tsc` fails:
- capture output
- verify changed files did not add `Cannot find module` errors
- run targeted grep and compile checks
- document residual pre-existing errors

---

## 9. Expected grep end states

### 9.1 Input

Must be zero in code paths:
```bash
rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" \
  frontend/lib frontend/components frontend/app frontend/types frontend/styles
```

Must be zero in canonical input and InvField panel:
```bash
rg "EntityPicker|entity-picker|EntitySearchModal" \
  frontend/lib/registry/components/input \
  frontend/components/v2/config-panels/InvFieldConfigPanel.tsx
```

### 9.2 Stats

Preferred end state:
```bash
rg -n "v2-aggregation-widget|v2-status-count|aggregation-widget" \
  frontend/lib frontend/components frontend/app frontend/types
```

Allowed matches only if:
- migration alias comments
- final preserved adapter with explicit reason
- old notes/test docs outside runtime path

Not allowed:
- auto-register old stats renderer without reason
- palette-visible old stats component
- old config panel direct route when canonical panel can handle it

### 9.3 Table

Do not require absolute zero immediately:
```bash
rg -n "v2-table-list|table-list|v2-split-panel-layout|split-panel-layout|split-panel-layout2" \
  frontend/lib frontend/components frontend/app frontend/types
```

Each remaining match must be classified:
- canonical alias/migration
- FlowWidget shared dependency
- domain/special component
- old saved layout compatibility
- todo with reason

### 9.4 Container

Do not require absolute zero immediately:
```bash
rg -n "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
```

Each remaining match must be classified:
- canonical alias/migration
- not-yet-equivalent repeater/conditional/accordion behavior
- domain/special dependency
- old saved layout compatibility

---

## 10. Verification commands

Minimum:
```bash
git status --short
git diff --check

rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" \
  frontend/lib frontend/components frontend/app frontend/types frontend/styles

rg "EntityPicker|entity-picker|EntitySearchModal" \
  frontend/lib/registry/components/input \
  frontend/components/v2/config-panels/InvFieldConfigPanel.tsx

cd backend-spring
./gradlew compileJava
```

Recommended frontend commands:
```bash
pnpm -s typecheck
npm run typecheck
npm run lint
```

Only run commands that exist in the repo. If package scripts differ, inspect `frontend/package.json` first.

Manual smoke:
- New `stats`: `user_info` count
- New `stats`: `user_info` count where `status = 재직`
- New `table`: load columns, select row, multi-select if enabled
- New `container`: tabs with one `stats` child and one `table` child
- New `chart`: group by a real column
- New `card-list`: select title/subtitle fields from a real table
- New `grouped-table`: group by a real column

---

## 11. Final report template for Claude

Use this structure at the end:

```md
## Summary
- ...

## Canonical paths updated
- ...

## Deleted
- ...

## Preserved legacy/V2 adapters
| Component/path | Reason | Next condition for deletion |
|---|---|---|
| ... | ... | ... |

## Verification
| Command | Result |
|---|---|
| git diff --check | pass/fail |
| input rg | pass/fail |
| stats rg | pass/fail/classified |
| table rg | pass/fail/classified |
| container rg | pass/fail/classified |
| ./gradlew compileJava | pass/fail |

## Remaining risks
- ...
```

---

## 12. Codex analysis snapshot

이 문서는 2026-05-18 현재 로컬 코드 분석 기준이다.

확인한 핵심 파일:
```text
frontend/types/invyone-component.ts
frontend/lib/dataPort/DataPortBus.ts
frontend/lib/dataPort/runtime.ts
frontend/lib/registry/components/index.ts
frontend/lib/registry/DynamicComponentRenderer.tsx
frontend/lib/utils/getComponentConfigPanel.tsx
frontend/lib/utils/templateMigrate.ts
frontend/lib/schemas/componentConfig.ts
frontend/lib/utils/layoutV2Converter.ts
frontend/components/screen/panels/ComponentsPanel.tsx
frontend/components/screen/InvyoneStudio.tsx
frontend/lib/registry/components/stats/*
frontend/lib/registry/components/table/*
frontend/lib/registry/components/container/*
frontend/lib/registry/components/chart/*
frontend/lib/registry/components/card-list/*
frontend/lib/registry/components/grouped-table/*
frontend/lib/api/stats.ts
backend-spring/src/main/java/com/erp/controller/TableManagementController.java
backend-spring/src/main/java/com/erp/service/TableManagementService.java
```

현재 dirty worktree에는 이 문서와 무관한 수정이 이미 있었다. Claude는 관련 없는 변경을 되돌리지 말아야 한다.
