Consolidate canonical input migration
Build & Deploy to K8s / build-and-deploy (push) Failing after 11m17s

Remove legacy v2 input/select and file/media runtimes, add canonical option/file loaders, and document Codex handoff.
This commit is contained in:
DDD1542
2026-05-12 18:36:43 +09:00
parent 90035dd5c6
commit 4a8413000b
79 changed files with 2359 additions and 14028 deletions
@@ -2,27 +2,27 @@
날짜: 2026-05-07 ~ 2026-05-11
작업자: gbpark
컨텍스트: 인비원스튜디오의 입력 컴포넌트 (input / v2-input / v2-select / 옛 6개) 가 분산되어 외형·모델 들쭉날쭉. **canonical 1안 (InvFieldConfigPanel + InputComponent)** 으로 통합 중.
컨텍스트: 인비원스튜디오의 입력 계열을 **FieldConfig / DataPort 계약을 지키는 canonical `input`** 으로 통합 중. 설정 패널은 계약을 편집하는 UI일 뿐, 진실의 원천은 `frontend/types/invyone-component.ts` 의 FieldConfig / DataPort.
---
## 0. 핵심 원칙
- INVYONE = VEX 의 2세대 리뉴얼. **운영 단계 아님** → 옛 키 fallback / 호환 부담 X. 깨끗한 canonical 1안.
- 옛것이 남아있으면 통합 아님. **1 패널 1 컴포넌트**.
- FieldConfig 가 유일한 필드 규격이고, DataPort / Connection 이 컴포넌트 통신 계약.
- 옛것이 남아있으면 통합 아님. V2 입력/선택은 구현체·alias·fallback·DB 마이그 대상이 아니라 제거 대상.
- GPT-5.5 (codex:rescue) 와 단계마다 교차 검증.
## 1. 큰 그림 (목표 형태)
```
[8 통합 컴포넌트] = [8 단일 ConfigPanel + 단일 캔버스 컴포넌트]
input → InvFieldConfigPanel + InputComponent (text/number/money/date/single/multi/autonum/formula/audit/file)
[8 통합 컴포넌트] = [FieldConfig/DataPort 계약을 소비하는 단일 캔버스 컴포넌트]
input → FieldConfig + DataPort + InputComponent (text/number/money/date/single/multi/autonum/formula/audit/file)
table → InvTableConfigPanel
search · button · title · divider · stats · container
[옛 V2 / 옛 6개 컴포넌트] → 기능 이식 후 폐기
V2Input.tsx (1286줄)
V2Select.tsx (1350줄)
옛 입력/선택 본체 2개 — 삭제 완료, 필요한 기능은 canonical input 으로 흡수
date-input / text-input / number-input / select-basic / checkbox-basic / textarea-basic 의 자체 캔버스 컴포넌트 6개
```
@@ -49,7 +49,7 @@
-`DynamicComponentRenderer.tsx:418-453` 의 fieldType / dbInputType → v2-input/v2-select **강제 swap 분기 통째 제거**
-`webTypeMapping.ts` 의 text 계열 9개 (text/email/password/tel/url/textarea/number/decimal/label) → `input`
- ✅ 기 fallback 도 `v2-input` `input`
-기 fallback 도 `input` canonical 로 정리했고, Phase D.2 에서 V2 입력/선택 fallback 자체 제거
### Phase 4 — InputComponent 외형 통일
@@ -96,7 +96,7 @@
-`RadioPicker` 신규 — single + mode=radio (라디오 button list)
-`CheckboxListPicker` 신규 — multi + mode=check (체크박스 list, maxSelect 차단)
- ✅ InputComponent select 분기 — mode 따라 4 picker 분기 (Single/Multi/Radio/CheckboxList)
- ✅ ConfigPanel "선택 방식" 옵션을 multi prop 따라 분기 — single: dropdown/combobox/radio/toggle, multi: dropdown/combobox/check/tag
- ✅ ConfigPanel "선택 방식" 옵션을 multi prop 따라 분기 — single: dropdown/combobox/radio/toggle, multi: dropdown/combobox/check/swap/tag
- ✅ TYPE_VOLATILE_FIELDS 에 `maxSelect` 추가 (`mode` 는 기존)
### Phase B.4 추가 정리 — 외각 box + 복수 선택 토글
@@ -117,6 +117,355 @@
## 2026-05-11 진행 (이번 세션)
### Phase C 보정 — entity 는 code-name 옵션 source (2026-05-12)
**배경**: 1차 C에서 `format=entity` 를 검색 모달로 해석했으나 사용자 점검으로 계약 오류 확인.
INVYONE input 의 entity 는 DB FK 를 직접 걸 수 없는 경우 화면 설정에서 참조 테이블과
저장 값/표시 라벨 컬럼을 지정해 code-name 옵션을 가져오는 선택형이다.
검색 모달은 별도 `entity-search-input` 계열의 책임이지 canonical input entity 의 기본 동작이 아니다.
**변경**:
-`useOptionLoader``source=entity``/entity/{entityTable}/options?value={entityValueColumn}&label={entityLabelColumn}` 로 로드
- `config.ref.table/valueColumn/displayColumn` 도 같은 계약으로 처리
-`InputComponent``type=single|multi + format=entity` 는 select 계열 picker 로 렌더
- 단일 entity: `SingleSelectPicker` (검색 가능 dropdown)
- 다중 entity: `MultiSelectPicker` / `mode=check` / `mode=swap` 지원
- ✅ 잘못 추가된 canonical input 전용 `EntityPicker` 모달 경로 제거
-`V2PropertiesPanel` / `InvFieldConfigPanel` — 참조 테이블 목록에서
`table_name/display_name``tableName/displayName` 을 모두 정규화
**미구현 (후속)**:
- 옵션 필터 (`filters`) 의 runtime query 전달
### Phase C.2 — v2-input/v2-select 재유입 차단 (2026-05-12)
**배경**: 목표는 V2 입력/선택 컴포넌트를 계속 쓰는 게 아니라, 필요한 기능만 `input` canonical 로 흡수하는 것. 그런데 webType 매핑과 renderer 우회로가 여전히 `v2-select` 를 생성/호출하고 있었음.
**변경**:
-`webTypeMapping.ts` — radio/checkbox/boolean/code/entity/category 모두 `componentType: "input"` 으로 변경
-`DynamicComponentRenderer.tsx` — category 고급 모드에서 `V2SelectRenderer` 를 직접 require 하던 분기 제거
-`components/index.ts``v2-input/V2InputRenderer`, `v2-select/V2SelectRenderer` auto-register import 제거
-`registerV2Components.ts``v2-input`, `v2-select` 레지스트리 등록 제거
-`InputComponent.tsx``format="entity"` 는 select 계열 picker 로 라우팅
-`useOptionLoader.ts``entityTable/entityValueColumn/entityLabelColumn` 로 code-name options 로드
-`ScreenSettingModal.tsx` — 폼 필드 자동 추가 시 `text-input` 대신 `input` canonical 생성
**검증**:
- `git diff --check` 통과
- `npx tsc --noEmit --pretty false` 는 기존 전역 타입 오류로 실패. 변경 파일 필터 기준 신규 오류 없음.
### Phase C — entity 선택형 보정 (2026-05-12 완료)
**배경**: input entity 는 마스터 데이터 (거래처/품목/사원) 의 참조 테이블에서 저장 값(code)과
표시 라벨(name)을 지정해 옵션으로 가져오는 기능. DB FK 강제 대신 화면 레벨에서 code-name 참조를 건다.
**변경**:
-`InputComponent.tsx` — case "entity" 는 모달이 아니라 `SingleSelectPicker` / `MultiSelectPicker`
-`useOptionLoader.ts` — entity source option loading 추가
-`frontend/lib/registry/components/input/entity-picker.tsx` 삭제 — canonical input 에 모달 검색 재유입 방지
**관련 파일 맵 업데이트**:
- canonical: `frontend/lib/registry/components/input/use-option-loader.ts``source=entity`
- 별도 검색 UI: `lib/registry/components/entity-search-input/` 는 명시 검색 컴포넌트가 필요할 때만 사용
### Phase C.3 — entity code-name 안정화 (2026-05-12)
**배경**: 1차 C 후 사용자 점검 — InvFieldConfigPanel 의 entity 설정 UI 에서 참조 테이블 dropdown 이
가끔 비고, value/label 컬럼을 바꾼 뒤에도 옵션이 갱신되지 않는 케이스 확인. 검색 모달 재유입은
없어야 한다는 계약은 그대로 유지.
**점검 결과 — 이미 정상**:
- `V2PropertiesPanel.normalizeConfigPanelTables``tableName/displayName``table_name/display_name` 모두 흡수.
InvFieldConfigPanel 에 `tables` / `allTables` 정규화된 값 전달
- `InvFieldConfigPanel.normalizeTableSelectOptions` — 동일하게 양쪽 키 흡수. EntityOptions 호출 시
`allTables.length > 0 ? allTables : tables` 폴백
- `InvFieldConfigPanel.loadColumnsForTable``columnName/column_name` + `displayName/display_name` 흡수
- `InputComponent.tsx` case "entity" — `SingleSelectPicker` / `MultiSelectPicker` / `CheckboxListPicker` /
`SwapPicker` 로만 라우팅. 모달 / EntityPicker import 없음
- `useOptionLoader.ts` source=entity — `/entity/{entityTable}/options?value={...}&label={...}` 로 fetch.
응답 shape (`data[]`, `data.success+data`, `data.options`) 정상 normalize
**수정 — fetchUrl dep 누락 (옵션 갱신 안 되는 진짜 원인)**:
- `use-option-loader.ts``fetchUrl` useMemo 의존성 배열에 다음 6개 추가:
- `config.entityTable` / `config.entityValueColumn` / `config.entityLabelColumn`
- `config.ref?.table` / `config.ref?.valueColumn` / `config.ref?.displayColumn`
- ref 객체 자체를 dep 으로 넣지 않은 이유: 객체 reference 변동만으로 effect 폭주를 방지하기 위해
내부 키만 추출
**디자인 모드 가드**:
- `useOptionLoader``isDesignMode === true``needsFetch = false` 로 fetch 자체 skip
- `InvFieldConfigPanel` 의 테이블/컬럼 목록 로드는 별도 경로 (apiClient 직접 호출, isDesignMode 무관)
→ 설정 패널 동작은 그대로 허용
**canonical input 에 모달 재유입 없음 (확인)**:
- `rg "EntityPicker|entity-picker|EntitySearchModal" frontend/lib/registry/components/input frontend/components/v2/config-panels/InvFieldConfigPanel.tsx` → 0건
### Phase C.4 — option filters runtime 적용 (2026-05-12)
**배경**: `OptionFilter[]` 가 InvFieldConfigPanel 에 저장만 되고 runtime 에 반영되지 않았음.
canonical input 의 entity / distinct / db source 에서 filter 를 실제 query 로 전달해 결과 옵션을 좁힌다.
검색 모달 재유입 / v2-input·v2-select 재도입 / FieldConfig·DataPort 축소는 모두 금지.
**변경**:
-`use-option-loader.ts``OptionLoaderConfig.filters?: OptionFilter[]` 추가
-`OptionUserContext` 타입 신규 — `companyCode / userId / deptCode / userName`
-`UseOptionLoaderArgs.userContext` 추가 — `value_type === "user"` 필터 치환용
-`resolveFilters(filters, formData, userContext)` 헬퍼 — 치환 규칙:
- `column` trim 후 빈 값 → skip
- `value_type=static``f.value` 그대로
- `value_type=field``formData[field_ref]`, 값 없으면 skip
- `value_type=user``userContext[user_field]`, 값 없으면 skip
- `isNull / isNotNull` → value 없이 통과
- `in / notIn` → 배열 또는 `"a,b,c"` 문자열 모두 trim 된 배열로 정규화
-`resolvedFiltersJson` useMemo — runtime 치환 결과를 `JSON.stringify` 로 stable string 화.
결과가 빈 배열이면 `undefined` (URL 에 query 자체 미추가)
-`fetchUrl` useMemo — `filters=` URL-encoded JSON 으로 append:
- **entity**: `/entity/{table}/options?value={v}&label={l}&filters={...}` (1순위)
- **distinct / select**: `/entity/{table}/distinct/{column}?filters={...}`
- **db (legacy)**: `/entity/{table}/options?value={v}&label={l}&filters={...}`
- **code / category / api**: 백엔드 endpoint 가 filter 스펙을 정의하지 않아 미적용 (TODO 주석 명시)
-`fetchUrl` dep 배열에 `resolvedFiltersJson` 추가 → filter 값 변경 시 URL 재생성 → cache key 자동 신규화
-`resolvedFiltersJson` 자체의 dep — `config.filters`, `formData`, `userContext` 4 키 (객체 reference 폭주 방지)
-`InputComponent.tsx` — props 에서 `companyCode / user_id / dept_code / user_name` 등 camel/snake 모두 흡수해
`userContext` 객체 구성 후 `useOptionLoader` 에 전달. DOM noise 누설 방지를 위해 destructure 목록에 4개 추가
**filter 치환 예시 — entity URL**:
```
config.entityTable = "tb_customer";
config.entityValueColumn = "customer_code";
config.entityLabelColumn = "customer_name";
config.filters = [
{ column: "is_active", operator: "=", value_type: "static", value: "Y" },
{ column: "company_code", operator: "=", value_type: "user", user_field: "companyCode" },
];
// runtime 사용자 companyCode = "C0001"
→ /entity/tb_customer/options?value=customer_code&label=customer_name
&filters=%5B%7B%22column%22%3A%22is_active%22%2C%22operator%22%3A%22%3D%22%2C%22value%22%3A%22Y%22%7D%2C%7B%22column%22%3A%22company_code%22%2C%22operator%22%3A%22%3D%22%2C%22value%22%3A%22C0001%22%7D%5D
```
**cache / race / design mode 영향**:
- **cache**: 기존 `responseCache: Map<url, options>`. filters 값이 바뀌면 URL 이 신규 → cache key 자동 신규.
이전 결과는 그대로 보존되어 같은 필터로 돌아오면 즉시 hit
- **race**: 기존 `reqIdRef` + `cancelled` flag 유지. URL 변경 시 in-flight 요청 결과 무시
- **design mode**: 기존 `isDesignMode === true``needsFetch=false` 로 fetch skip 흐름 그대로. filter resolver
는 dep 평가 단계에서만 도는 정도이고 API 호출 자체가 없음
**InvFieldConfigPanel UI**: 이미 filters 입력 UI 존재. 이번 단계는 runtime 전달만 보완, UI/문구 변경 없음.
**검증**:
- `git diff --check` — whitespace 0
- `rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" frontend/lib frontend/components frontend/app frontend/types frontend/styles` — 0건
- `rg "EntityPicker|entity-picker|EntitySearchModal" frontend/lib/registry/components/input frontend/components/v2/config-panels/InvFieldConfigPanel.tsx` — 0건
- `tsc` 변경 파일 (`use-option-loader`, `InputComponent`) 신규 오류 0건
### Phase D.4 — file / image / img → canonical input 통합 (2026-05-12)
**배경**: webTypeMapping 의 `file / image / img` 와 DynamicWebTypeRenderer 의 강제 분기가
여전히 `v2-file-upload / FileUploadComponent / ImageWidget` 로 빠지고 있었음. canonical input
에 file 분기는 기존부터 있었지만 native `<input type=file>` 단독이라 미리보기 / 다중 칩 / 삭제
UX 가 빠져 있었음. 옛 본체 파일 삭제는 사용처 0건 확인 후 별도 phase.
**변경**:
-`frontend/lib/registry/components/input/file-picker.tsx` 신규 — `FilePicker`
- props: `value / onChange / accept / multiple / maxFiles / disabled / readonly / placeholder / showPreview / className`
- 값 형태: `File | string | Array<File | string> | null` (서버 저장된 path 문자열도 흡수)
- 이미지면 `URL.createObjectURL` preview thumbnail (cleanup 효과 포함, 메모리 누수 방지)
- SSR/Node 경로에서 `File` 전역이 없을 수 있어 `typeof File !== "undefined"` 가드 적용
- 그 외 파일은 `FileText` 아이콘 + 파일명 (max-w-160 truncate + tooltip)
- 다중 시 `maxFiles` 초과 자르기 + 한도 도달 시 `+추가` 버튼 disabled
- `disabled / readonly``lockEdit` 통합 (선택/삭제/추가 모두 차단)
- 같은 파일 재선택 가능하도록 hidden input value 매번 초기화
-`frontend/lib/registry/components/input/types.ts``InputConfig``maxFiles / showPreview` 추가
-`frontend/lib/registry/components/input/InputComponent.tsx` — case "file"
- native `<input type=file>` 단독 → **FilePicker** 호출
- `format === "image"` 면 자동 `accept="image/*"` + `showPreview=true`
- `format === "doc"``.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx`
- 사용자 명시 `accept / showPreview` 가 있으면 그 값 우선
- fallback webType 에 `img / picture / photo` 도 file 로 흡수
-`frontend/lib/utils/webTypeMapping.ts`
- `file / image / img``WEB_TYPE_V2_MAPPING` 항목 → `componentType: "input"` 으로 변경
- config triple: `{ kind: "attach", type: "file", format: "file" | "image", accept, multiple, maxFiles, showPreview }`
- `WEB_TYPE_COMPONENT_MAPPING``file/image/img → "input"`
-`frontend/lib/registry/DynamicWebTypeRenderer.tsx`
- `webType === "file" / "image" / "img" / "picture" / "photo"``props.component?.type === "file"` 모두
canonical `InputComponent` 로 라우팅 (file config triple 주입). 옛 `FileUploadComponent` / `ImageWidget`
직접 import 분기 3곳 제거 (참조만 끊고 파일 자체는 잔존 — 별도 phase)
- 기존 타입 mismatch 보정: `webType/web_type` 양쪽 prop 흡수, `defaultConfig``default_config`
-`frontend/components/screen/panels/ComponentsPanel.tsx` — hidden 목록 주석 갱신
- "→ V2Media" / "→ v2-media (image)" / "통합 미디어" 등을 "→ canonical input (type='file', format='image')" 로 우회
- `v2-media` / `v2-file-upload` 두 항목은 hidden 유지 (생성 차단)
-`notes/...` 본 노트 — Phase D.4 기록
**file / image / img 가 이제 어떤 config 로 input 생성되는지 예시**:
```jsonc
// webType = "file"
{
"componentType": "input",
"config": {
"kind": "attach",
"type": "file",
"format": "file",
"accept": "*/*",
"multiple": true,
"maxFiles": 10
}
}
// webType = "image" 또는 "img"
{
"componentType": "input",
"config": {
"kind": "attach",
"type": "file",
"format": "image",
"accept": "image/*",
"multiple": false,
"maxFiles": 1,
"showPreview": true
}
}
```
**FieldConfig / DataPort 영향**: **0건**
- `frontend/types/invyone-component.ts` 의 FieldType / FieldConfig / DataPort / Connection 변경 없음
- `frontend/lib/dataPort/runtime.ts` 변경 없음
- 추가된 `InputConfig.maxFiles` / `InputConfig.showPreview` 는 input 컴포넌트 내부 옵션 (FieldConfig 와는 별개 — InputComponent 의 InvField triple 흡수 후 props 분리)
**남은 old file/media 삭제 후보 (다음 phase)**:
- `frontend/lib/registry/components/file-upload/FileUploadComponent.tsx` + `index.ts` + `FileUploadConfigPanel.tsx`
- `frontend/lib/registry/components/v2-file-upload/` 폴더 통째
- `frontend/lib/registry/components/v2-media/` 폴더 통째 (`V2MediaRenderer` + config 패널)
- `frontend/components/v2/V2Media.tsx` 본체 + V2Media 관련 타입 / 데모 / 매핑
- `frontend/lib/registry/components/image-widget/` + `image-display/`
- `frontend/components/screen/widgets/types/ImageWidget.tsx`
- **사전 확인 필요**: 위 컴포넌트들이 외부에서 import 되는 경로 grep 후 0건일 때만 삭제
**검증**:
-`git diff --check` — whitespace 0
-`rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer"` — 0건 유지
-`rg "componentType: \"v2-file-upload\"|file: \"v2-file-upload\"|image: \"v2-file-upload\"|img: \"v2-file-upload\""` — 새 생성/매핑 경로 **0건**
-`rg "EntityPicker|entity-picker|EntitySearchModal" canonical input 경로` — 0건 유지
-`tsc` 변경 파일 신규 오류 0건 (`DynamicWebTypeRenderer` 의 기존 `webType`/`defaultConfig` mismatch 도 함께 정리)
### Phase D.5 — canonical file 저장 파이프라인 + old file/media 런타임 차단 (2026-05-12)
**배경**: Phase D.4 에서 file/image/img 새 생성 경로는 canonical input 으로 통합. 본 phase 는
(1) canonical input file 값이 master save 흐름에서 누락되지 않게 보완 + (2) 옛 file-upload /
image-widget / image-display / v2-media / v2-file-upload 의 런타임 / registry / ConfigPanel
경로 차단. 옛 본체 파일 자체 삭제는 외부 import 정밀 분석 후 별도 phase.
**canonical file 저장값 변환 정책 (master save)**:
- `InteractiveScreenViewerDynamic.handleSaveAction``masterFormData` 구성 단계 보강
- 파일 컬럼 식별 (`isFileColumnComponent`):
- 옛: `componentType ∈ {v2-media, file-upload}` 또는 `url.includes(...)` (호환용)
- canonical: `componentType === "input"` && (`config.type === "file"` || `config.kind === "attach"` || `config.format ∈ {file, image, doc}`)
- 보정: `componentConfig / config / overrides` 세 저장 위치를 모두 흡수. 컬럼명도 `column_name / columnName` 양쪽 키를 같은 우선순위로 확인.
- 값 별 처리:
- `null / undefined` → 그대로
- `string / string[]` → 이미 저장된 path/id 로 보고 유지 (업로드 skip)
- `File / File[]``uploadFiles` (POST `/files/upload`) 호출 → 응답 `FileInfo[].id ?? server_path ?? server_filename` 로 치환
- 혼합 (`(File | string)[]`) → string 유지 + File 만 업로드 후 합쳐서 배열로
- 업로드 실패 시 안전하게 string 만 유지 (브라우저 `File` 객체가 saveData 에 들어가지 않도록)
- 파일 컬럼이 아닌 배열 값은 종전대로 repeater 로 보고 제외
- 파일 컬럼은 더 이상 repeater 오인 제외 대상이 아님
**제거한 old runtime / register / config 경로**:
- `InteractiveScreenViewerDynamic.tsx``FileUploadComponent` import 제거, `isFileComponent` import 제거,
`if (isFileComponent(comp)) return renderFileComponent(comp)` 분기 제거, `renderFileComponent` 함수 통째 제거,
`uploadFilesAndCreateData` 직접 import 제거 (`uploadFiles` 동적 import 가 master save 안에서 대체)
- `lib/registry/components/index.ts``file-upload/FileUploadRenderer`, `image-widget/ImageWidgetRenderer`,
`image-display/ImageDisplayRenderer`, `v2-media/V2MediaRenderer`, `v2-file-upload/V2FileUploadRenderer`
auto-register import 5개 모두 제거
- `lib/utils/getComponentConfigPanel.tsx``CONFIG_PANEL_MAP``v2-media / file-upload / image-display / image-widget` mapping 4개 제거
- `components/screen/panels/V2PropertiesPanel.tsx``v2ConfigPanels``v2-media` 매핑 제거
- `lib/schemas/componentConfig.ts``v2MediaOverridesSchema` 정의 + `componentOverridesRegistry``v2-media` 등록 + `componentDefaultsRegistry``v2-media` 기본값 모두 제거
- `components/v2/registerV2Components.ts``V2Media` / `V2MediaConfigPanel` import + v2-media `ComponentDefinition` 등록 제거
- `components/v2/V2ComponentRenderer.tsx``isV2Media` / `V2Media` import + `if (isV2Media(props)) return <V2Media>` 분기 제거
- `components/v2/V2ComponentsDemo.tsx``V2Media` import + media TabsTrigger + media TabsContent 통째 제거
- `components/v2/index.ts``V2Media` export + V2Media 타입 re-export (`V2MediaType / V2MediaConfig / V2MediaProps`) 제거
- `components/v2/config-panels/index.ts``V2MediaConfigPanel` export 제거
- `types/v2-components.ts``V2MediaType / V2MediaConfig / V2MediaProps` 인터페이스 + `isV2Media` 가드 + `V2ComponentType` / `V2ComponentProps` 유니온의 V2Media 멤버 + `LEGACY_TO_V2_MAP` 의 file-upload/image-widget → V2Media 매핑 제거
- `components/v2/V2Media.tsx` — D.6 삭제 전까지 전체 tsc 가 중앙 타입 제거로 바로 깨지지 않도록 로컬 orphan `V2MediaProps` 만 임시 정의. 런타임/register/config 경로는 이미 차단됨.
- `app/(main)/screens/[screenId]/page.tsx``compType?.includes("v2-media")` / `compType?.includes("file-upload")` 중복 매칭 제거 (이미 `includes("input")` 으로 흡수됨)
**보존한 shared file viewer 유틸**:
| 파일 | 보존 이유 |
|---|---|
| `frontend/lib/registry/components/file-upload/FileViewerModal.tsx` | `GlobalFileViewer.tsx`, `FileAttachmentDetailModal.tsx` 가 import. 단독 파일 뷰어 모달. canonical input 과 무관한 shared util |
| `frontend/lib/registry/components/file-upload/FileManagerModal.tsx` | `FileUploadComponent` (옛 본체) 가 사용. FileViewerModal 사용. 본체 삭제 시 함께 검토 |
| `frontend/lib/registry/components/file-upload/types.ts` (있다면) | FileViewerModal/FileManagerModal 의 타입 의존성 |
**검증**:
-`git diff --check` — 0건
-`rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer"` — 0건
-`rg "componentType: \"v2-file-upload\"|file: \"v2-file-upload\"|image: \"v2-file-upload\"|img: \"v2-file-upload\""` — 0건
-`rg "EntityPicker|entity-picker|EntitySearchModal" canonical input 경로` — 0건
-`rg "FileUploadComponent" frontend/lib frontend/components frontend/app frontend/types`:
- 잔존 매치는 모두 (a) 폴더 내부 자기 자신 정의 (`file-upload/`, `v2-file-upload/`) (b) 폴더 내부 V2Media 본체의 import (V2Media 도 폐기 표시됨) (c) Phase D.4/D.5 주석. **외부 runtime 직접 import 0건**
-`rg "V2Media|v2-media|V2FileUpload|v2-file-upload"`:
- 잔존은 폐기 폴더 내부 정의 / 옛 본체 (V2Media.tsx / V2FileUploadConfigPanel.tsx) / 폐기 주석. **외부 신규 runtime/config/register 참조 0건**
-`tsc` 변경 파일 신규 오류 0건 (전역 잔존 오류는 모두 기존 camelCase ↔ snake_case 미스매치)
**다음 phase 에서 실제 삭제 가능한 파일/폴더 (사전 import 0건 재확인 필요)**:
| 경로 | 사전 확인 명령 | 비고 |
|---|---|---|
| `frontend/lib/registry/components/v2-file-upload/` (FileViewerModal/FileManagerModal 제외 가능성 검토) | `rg "v2-file-upload\\|V2FileUpload"` | 폴더 자체 정의만 잔존 |
| `frontend/lib/registry/components/v2-media/` | `rg "v2-media\\|V2MediaRenderer"` | 폴더 자체 정의만 잔존 |
| `frontend/components/v2/V2Media.tsx` | `rg "V2Media\\b"` | 외부 import 0건 확인됨 |
| `frontend/components/v2/config-panels/V2MediaConfigPanel.tsx` | `rg "V2MediaConfigPanel"` | 외부 import 0건 확인됨 |
| `frontend/components/v2/config-panels/V2FileUploadConfigPanel.tsx` | `rg "V2FileUploadConfigPanel"` | 외부 사용 없음 |
| `frontend/lib/registry/components/image-widget/` | `rg "image-widget\\|ImageWidget"` | DynamicWebTypeRenderer 의 옛 ImageWidget import 는 Phase D.4 에서 제거됨 |
| `frontend/lib/registry/components/image-display/` | `rg "image-display"` | config panel mapping 제거됨 |
| `frontend/lib/registry/components/file-upload/FileUploadComponent.tsx`, `FileUploadRenderer.tsx`, `FileUploadConfigPanel.tsx` | 위 grep | **FileViewerModal / FileManagerModal / types 만 잔존시키고 본체 3 파일만 삭제** |
| `frontend/components/screen/widgets/types/ImageWidget.tsx` | `rg "ImageWidget"` | DynamicWebTypeRenderer 분기 제거됨 |
**Phase D.6 (제안)**: 위 후보를 각각 grep 0건 확인 후 `git rm`. `FileUploadComponent` 본체만 삭제하고 `FileViewerModal / FileManagerModal / types` 는 보존 (GlobalFileViewer / FileAttachmentDetailModal 의존성).
### Phase D.6 — old file/media 본체 실제 삭제 (2026-05-12)
**배경**: Phase D.4 / D.5 에서 file/image/img 진입 경로는 canonical input + FilePicker 로 전부 통합되고,
옛 file/media 본체의 runtime / register / config 경로도 차단됨. 이 phase 는 사용처 0건이 된 옛 본체를
실제 `git rm` 으로 삭제. shared file viewer 유틸은 `GlobalFileViewer` / `FileAttachmentDetailModal` /
`FileComponentConfigPanel` 의존성이 있어 보존.
**삭제 (git rm)**:
- 폴더 통째: `lib/registry/components/v2-file-upload/` (8 파일) · `v2-media/` (2 파일) · `image-widget/` (3 파일) · `image-display/` (6 파일)
- 본체 파일: `components/v2/V2Media.tsx` · `components/v2/config-panels/V2MediaConfigPanel.tsx` · `components/v2/config-panels/V2FileUploadConfigPanel.tsx` · `components/screen/widgets/types/ImageWidget.tsx`
- file-upload 본체 3 파일: `FileUploadComponent.tsx` · `FileUploadRenderer.tsx` · `FileUploadConfigPanel.tsx` + `config.ts` + `README.md`
**보존 (shared file viewer 유틸)**:
- `lib/registry/components/file-upload/FileViewerModal.tsx``GlobalFileViewer` / `FileAttachmentDetailModal` 의존
- `lib/registry/components/file-upload/FileManagerModal.tsx` — FileViewerModal 사용
- `lib/registry/components/file-upload/types.ts``FileInfo` / `FileUploadConfig` / `FileUploadResponse` 가 4 곳에서 사용
**정리 (잔여 참조)**:
- `file-upload/index.ts` 재작성 — FileUploadComponent / FileUploadRenderer / FileUploadConfigPanel re-export 제거.
`FileViewerModal` / `FileManagerModal` + 타입 3종 (`FileInfo` / `FileUploadConfig` / `FileUploadResponse`) 만 export 하는 shim
- `components/screen/widgets/types/index.ts``ImageWidget` import / export / `getWidgetComponentByName``case "ImageWidget"` /
`getWidgetComponentByWebType` 의 image/img/picture/photo 분기 / `WebTypeComponents``image` 매핑 모두 제거
**잔존 grep 매치 분류 (모두 운영 영향 없음)**:
- 폐기 안내 주석 / 작업 노트
- `WidgetRenderer.tsx:41``isImageWidget` 변수 — widgetType 문자열 (`image/img/picture/photo`) 매칭만. ImageWidget 본체 import 없이 wrapper `pointer-events-none` 처리, 실제 렌더는 `DynamicWebTypeRenderer` → canonical input (Phase D.4)
- `pop-components/pop-text.tsx``ImageDisplay` — POP 컴포넌트 영역의 내부 helper 함수. 폐기된 `image-display/` 폴더와 무관
**검증**:
-`git diff --check` — 0건
-`rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer"` — 0건
-`rg "componentType: \"v2-file-upload\"|file: \"v2-file-upload\"|image: \"v2-file-upload\"|img: \"v2-file-upload\""` — 0건
-`rg "EntityPicker|entity-picker|EntitySearchModal" canonical input 경로` — 0건
-`rg "FileUploadComponent|FileUploadRenderer|FileUploadConfigPanel|V2Media\b|V2MediaConfigPanel|V2FileUpload|ImageWidget|ImageDisplay"`:
- 잔존은 모두 폐기 주석 / `isImageWidget` 변수 / `pop-text` 의 ImageDisplay 내부 helper. **외부 runtime / config / register 직접 import 0건**
-`tsc --noEmit` — 삭제로 인한 `Cannot find module` 오류 0건. 잔존 3건은 모두 D.6 과 무관한 기존 module 오류 (`hierarchyColumn` / `auto-animate` / `lib/types/screen`)
**파일 통계**:
- 삭제: 13 단일 파일 + 4 폴더 (총 ~30 파일)
- 수정: 2 파일 (`file-upload/index.ts` shim 재작성, `widgets/types/index.ts` ImageWidget 정리)
### Phase A.8 — 채번 admin 페이지 + 시퀀스 관리 (★ 별도 트랙)
**배경**: VEX 는 채번을 캔버스 컴포넌트로 처리 (잘못된 구조). 팀장 요구 — 별도 admin 페이지에서 채번 규칙 + 시퀀스 일원 관리. INVYONE 에는 그 기능 없음 → 신규 작성.
@@ -198,7 +547,7 @@
- `rulesRefreshKey` state + numberingRules effect dep 추가
- 생성 후 새 ruleId 자동 선택 + rules 목록 refresh
- **canonical 위치 통일**: `autoGeneration.numberingRuleId` (옵션 밖) → `autoGeneration.options.numberingRuleId` (옵션 안)
- V2InputConfigPanel 도 옵션 안에 저장. autoGeneration.ts.generateValue 도 옵션 안 사용.
- 옛 입력 설정 패널도 옵션 안에 저장. autoGeneration.ts.generateValue 도 옵션 안 사용.
- InputComponent 의 NumberingPicker `numberingRuleId` prop 도 옵션 안에서 읽음 → 일관 ★
- 이전 옵션 밖 fallback 제거 (canonical 1안 원칙)
- ✅ Codex 검증 — 2 issue fix
@@ -210,7 +559,7 @@
### Phase A.6 — numbering API hook (완료)
-`frontend/lib/registry/components/input/numbering-picker.tsx` 신규
- V2Input.tsx:600~949, 1069~1144 의 채번 본체 추출 — useState/useRef + 3 useEffect (main / debounce / beforeFormSave) + 렌더 (readonly text vs prefix-input-suffix)
- 옛 입력 컴포넌트의 채번 본체 추출 — useState/useRef + 3 useEffect (main / debounce / beforeFormSave) + 렌더 (readonly text vs prefix-input-suffix)
- `previewNumberingCode(ruleId, formData, manualInputValue)` API 사용
- ruleId 결정 흐름: `props.numberingRuleId` 우선 → 없으면 `by-column` API → fallback `getTableColumns.detailSettings.numberingRuleId`
- `____` 템플릿: 첫 생성 시 templateRef set + 부모 value, 카테고리 변경 시 manualInputValue 유지 + 새 조합값 onChange
@@ -243,7 +592,7 @@
- ✅ Radio/Checkbox list 의 **외각 box 제거**`mode=radio||check` 시 container border/bg 없음 (자체 visual element 가 표시)
-**"복수 선택" CPSwitch 제거** (고급 설정) — brumb 의 `단일/다중` 이 진실의 원천. 단일/다중 = 저장 형태 차이 (값 cardinality), UX 토글 아님
- ✅ "최대 개수" 노출 조건 — `multi prop` 으로 분기 (config.multiple 의존 X)
- ✅ ConfigPanel "선택 방식" 옵션을 multi 따라 분기 — single: dropdown/combobox/radio/toggle, multi: dropdown/combobox/check/tag
- ✅ ConfigPanel "선택 방식" 옵션을 multi 따라 분기 — single: dropdown/combobox/radio/toggle, multi: dropdown/combobox/check/swap/tag
-~~`displayMode` 신규 prop~~ 폐기 — 기존 `config.mode` 와 중복이라 정리. mode 만 사용
- ✅ Radio/Checkbox list — flex-col → flex-wrap 으로 변경 (디자인/운영 모드 외형 일관, 박스 사이즈에 맞게 자동 wrap)
- ✅ picker 4개 wrapper 의 `opacity-50` 제거 (시각 흐릿 해소)
@@ -299,6 +648,115 @@ const runtimeConfig =
- `templateAdapter.ts:76``[saveTemplate] payload` 일반 진단용 로그 — 유지 (low-noise)
- 그 외 [Input debug] / [Save layout debug] / [loadTemplate] / [Input render path] 등 모두 제거 완료
### Phase B.5 — option loader (2026-05-12)
**배경**: InputComponent 의 select 분기가 `componentConfig.options` 정적 배열만 처리했음.
공통코드 (`source=code`) / 사용자 카테고리 (`source=category`) / DB distinct (`source=distinct`)
/ 외부 API (`source=api`) 케이스는 옛 선택 컴포넌트의 옵션 로딩 로직에 갇혀 있어서 input
canonical 만으로는 옵션이 비어 보였음.
**변경**:
-`frontend/lib/registry/components/input/use-option-loader.ts` 신규 hook
- 입력 : `config / tableName / columnName / formData / isDesignMode`
- 처리 source : `static / code / category / distinct (=select) / api / db`
- 응답 정규화 : `Array<{value, label}>``value/code/id/valueCode` + `label/name/valueLabel`
여러 응답 shape 흡수. category 트리는 `flattenCategoryTree` 로 평탄화 (`valueCode` 우선)
- 디자인 모드 가드 : `isDesignMode === true` 면 fetch 자체 skip → static 만
- 캐시 : module-scoped `Map<url, options>` 로 같은 url 재호출 차단
- race 방지 : 요청 id (`reqIdRef`) + `cancelled` flag
- 계층 (`hierarchical + parentField`) : `parentValue``formData[parentField]` 에서
오면 `/common-codes/categories/{group}/hierarchy?parentCodeValue=...` 로 자식 코드 조회
- entity 는 참조 테이블의 value/label 컬럼을 code-name 옵션으로 로드
- 실패 시 `console.warn` + 빈 옵션 (컴포넌트가 죽지 않음)
-`InputComponent.tsx` 본체 — `useOptionLoader` 호출 + select 분기에서 `loadedOptions`
를 picker 들 (SingleSelectPicker / MultiSelectPicker / RadioPicker / CheckboxListPicker)
에 직접 전달. 정적 정규화 코드 (rawOptions.map) 제거
- ✅ FieldOption (string | object) 과 SelectOption (object) 의 union vs concrete 충돌
해소를 위해 loader 가 자체 `LoadedOption` 타입 정의해 반환
**구현한 source**: static / code / category / distinct / select / api / db (V2 legacy)
**TODO 로 남은 source / 기능**:
- 다단 cascade (계층 외에 sourceFiltering 으로 N→1 의존)
- 옵션 검색 `filters` (`OptionFilter[]`) — runtime 치환 후 query string 전달.
InvFieldConfigPanel 에 filter UI 가 아직 없어서 데이터 자체가 없음. config 가 들어오면
쉽게 확장 가능하도록 hook 내부에 자리만 잡아둠 (현재는 미적용)
-~~swap 모드~~ (B.4.5 — 2026-05-12 완료)
**검증**:
-`git diff --check` 통과 (whitespace 0)
- ✅ `rg V2SelectRenderer | v2-input/V2InputRenderer | id: "v2-input" | id: "v2-select" |
componentType: "v2-input/select" | type: "v2-input/select"` — 외부 import 0건
(`v2-input` / `v2-select` 렌더러 폴더 자체 정의는 Phase D에서 삭제 완료)
- ✅ `npx tsc --noEmit --pretty false` 변경 파일 (InputComponent / use-option-loader)
타입 오류 0건. `DateInputComponent.tsx:214` 의 기존 옛 에러는 Phase E 폐기 예정
### Phase D.1 — v2-input/v2-select 렌더러 파일 삭제 (2026-05-12)
**배경**: Phase C.2/B.5 이후 `v2-input` / `v2-select` 는 더 이상 registry/import 경로에서 쓰이지 않음. 새 솔루션 개발 기준이므로 기존 저장 화면 호환 / DB layout 마이그레이션은 목표가 아님.
**변경**:
- ✅ `frontend/lib/registry/components/v2-input/V2InputRenderer.tsx` 삭제
- ✅ `frontend/lib/registry/components/v2-input/index.ts` 삭제
- ✅ `frontend/lib/registry/components/v2-select/V2SelectRenderer.tsx` 삭제
- ✅ `frontend/lib/registry/components/v2-select/index.ts` 삭제
- ✅ 혼선 방지용 주석 정리 — 새 생성 경로는 `input` canonical 이며, legacy alias/fallback 은 Phase D.2 에서 제거 대상
**검증**:
- ✅ `rg components/v2-input|components/v2-select|V2InputRenderer|V2SelectRenderer` — 실제 참조 0건
- ✅ `rg id/componentType/type: "v2-input|v2-select"` — 신규 생성 literal 0건
- ✅ `git diff --check` 통과
### Phase B.4.5 — SwapPicker (2026-05-12)
**배경**: InputComponent 의 select multi 분기가 `dropdown/multi/radio/check/tag` 까지 처리하지만
`mode=swap` 은 미구현. 양쪽 리스트로 항목을 이동하는 UI 가 옛 다중 선택의 표준 패턴 중 하나라
canonical input 에 흡수.
**변경**:
- ✅ `frontend/lib/registry/components/input/select-pickers.tsx` — `SwapPicker` 신규 export
- props: `value: string[] / onChange / options / maxSelect / disabled / readonly / className / availableLabel / selectedLabel`
- UI: 왼쪽 (available, options 순서) ↔ 가운데 (`ChevronRight`/`ChevronLeft` 버튼) ↔ 오른쪽 (selected, value 순서)
- 항목 클릭 → 같은 패널 내 highlight 토글 (다중 선택). 이동 후 highlight 자동 해제
- 순서 정책: left→right 이동 시 selected 끝에 append, right→left 이동 시 그 항목만 제거
- options 에 없는 value 가 들어와도 selected 에 표시 (label = value fallback)
- opt.disabled / 패널 lockEdit 항목은 클릭 차단 + opacity 60%
- ✅ `frontend/lib/registry/components/input/InputComponent.tsx` — select multi 분기 `mode === "swap"` 추가
- 위치: tag → check → **swap** → dropdown/combobox (기본) 순. 기존 분기 보존
- `loadedOptions` 그대로 전달, `maxSelect` / disabled / readonly 동일하게 전달
- ✅ `frontend/components/v2/config-panels/InvFieldConfigPanel.tsx` — 고급 설정의 `선택 방식` 에
`swap` 노출. `multi + list` 는 패널 설명처럼 기본 `mode=check` 로 정렬
**maxSelect / readonly / disabled 처리**:
- `maxSelect`: left→right 이동 시 `selected.length + additions.length > maxSelect` 면 `slice(0, maxSelect)` 로 잘림.
selected 가 이미 한도면 right 화살표 버튼 자체 비활성 (`atLimit`).
- `readonly` / `disabled`: 단일 `lockEdit` 플래그로 통합. 항목 클릭, 화살표 버튼, 패널 hover 효과 모두 비활성.
컨테이너에 `cursor-not-allowed` 부착.
**검증**:
- ✅ `git diff --check` 통과 (whitespace 0)
- ✅ `rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer"` — 0건 유지
- ✅ `rg "V2Input|V2Select|V2InputConfigPanel|V2SelectConfigPanel"` — `V2SelectedItemsDetailInputConfigPanel`
substring 매치만 잔존 (unrelated 컴포넌트)
- ✅ `npx tsc --noEmit` — InputComponent / select-pickers / use-option-loader 신규 오류 0건
### Phase D.2 — v2-input/v2-select alias/fallback/schema 완전 제거 (2026-05-12)
**배경**: 목표는 InvFieldConfigPanel UI 호환이 아니라 FieldConfig / DataPort 계약 호환. V2 입력/선택 구현체를 runtime alias/fallback/schema 로 살리지 않고, 필요한 기능만 canonical `input` 에 흡수한다.
**변경**:
- ✅ `DynamicComponentRenderer.tsx` — legacy alias / V2SelectRenderer 직접 require / 입력·선택 특수 처리 제거
- ✅ `getComponentConfigPanel.tsx`, `V2PropertiesPanel.tsx` — V2 입력/선택 패널 매핑 제거
- ✅ `componentConfig.ts` — V2 입력/선택 override schema / default config 제거
- ✅ 화면/디자이너/aggregation 패널/CSS/templateMigrate 주석·분기 제거
- ✅ `InputComponent.tsx` — 옛 `rawType` fallback 분기 제거
**검증**:
- ✅ `git diff --check` 통과
- ✅ `rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" frontend/lib frontend/components frontend/app frontend/types frontend/styles` — 0건
- ✅ `rg "components/v2-input|components/v2-select|v2-input/V2InputRenderer|v2-select/V2SelectRenderer" frontend` — 0건
- ✅ FieldConfig / DataPort 계약 축소 없음. `types/invyone-component.ts` 변경은 entity FieldRef 확장(autoFill/filter/modalColumns)만.
---
## 3. 진행 중 / 남은 작업
@@ -312,9 +770,10 @@ const runtimeConfig =
- ✅ ~~**B.2** MultiSelectPicker — multi / maxSelect~~ (완료)
- ✅ ~~**B.3** TagPicker — tags (tagbox)~~ (완료)
- ✅ ~~**B.4** radio / checkbox / toggle~~ (완료)
-**B.4.5** SwapPicker — multi + mode=swap (양쪽 list 간 이동, 큰 작업)
-**B.5** option loader — api / code / category / distinct / 계층 (apiClient 의존)
- webTypeMapping 의 multi / checkbox / radio / boolean / code / category 매핑도 점진 input
- ✅ ~~**B.4.5** SwapPicker — multi + mode=swap~~ (2026-05-12 완료 — select-pickers.tsx 에 신규)
- ✅ ~~**B.5** option loader — api / code / category / distinct / 계층~~ (2026-05-12 완료 — `use-option-loader.ts` 신규)
- ✅ webTypeMapping 의 checkbox / radio / boolean / code / category / entity 매핑 → input
- ⏳ 실제 옵션 주입 검증 — 공통코드 / 카테고리 데이터 있는 화면에서 운영 동작 확인 필요
### 디버그 (해결)
@@ -322,18 +781,35 @@ const runtimeConfig =
### Phase C — entity
- entity 검색팝업 + 다른 컬럼 auto-fill (V2Select.tsx:1007~)
- ⏳ 현재 InputComponent entity 분기는 placeholder 버튼만
- ✅ entity code-name 옵션 로딩 (`/entity/{table}/options?value=...&label=...`)
- ✅ InputComponent entity 분기 → Single/Multi select picker
- ✅ multi entity (`type="multi" + format="entity"`) 는 JSON 배열 저장 형태로 select 계열 picker 재사용
### Phase D — V2 폐기
- webTypeMapping 의 select 계열 (multi/category/entity/code/checkbox/radio/boolean) → input (B 단계 후)
- ✅ webTypeMapping 의 select 계열 (category/entity/code/checkbox/radio/boolean) → input
- ⏳ webTypeMapping 의 file/image/img → input (file 통합 후)
-`ScreenSettingModal.tsx:2052-2066` 의 옛 컴포넌트 생성 경로 → input
- ⏳ DB 마이그 — `screens` 테이블의 `layout` JSON 안 componentType 변경 (사용자 승인 필요)
-`V2Input.tsx` / `V2Select.tsx` 파일 삭제
-`registerV2Components.ts` 의 v2-input / v2-select 등록 제거
-`V2InputConfigPanel.tsx` (832줄) 삭제 — 이전 세션의 폐기 보류
- ✅ `ScreenSettingModal.tsx:2052-2066` 의 옛 컴포넌트 생성 경로 → input
- ✅ DB layout JSON 마이그레이션 안 함 — 새 솔루션 개발 기준, 기존 V2 저장 데이터 보존은 목표 아님
- ✅ `frontend/lib/registry/components/v2-input/` 및 `v2-select/` 렌더러 파일 삭제
- ✅ `registerV2Components.ts` 의 v2-input / v2-select 등록 제거
- ✅ `components/index.ts` 의 v2-input / v2-select renderer auto-register 제거
- ✅ v2-input / v2-select alias / fallback / schema 제거
- ✅ `V2Input.tsx` / `V2Select.tsx` 본체 및 `V2InputConfigPanel.tsx` / `V2SelectConfigPanel.tsx` 삭제
### Phase D.3 — V2Input / V2Select 본체 및 직접 참조 삭제
- ✅ `frontend/components/v2/V2Input.tsx` 삭제
- ✅ `frontend/components/v2/V2Select.tsx` 삭제
- ✅ `frontend/components/v2/config-panels/V2InputConfigPanel.tsx` 삭제
- ✅ `frontend/components/v2/config-panels/V2SelectConfigPanel.tsx` 삭제
- ✅ `components/v2/index.ts`, `config-panels/index.ts`, `V2ComponentRenderer.tsx`, `V2ComponentsDemo.tsx`, `DynamicConfigPanel.tsx` 직접 참조 제거
- ✅ `types/v2-components.ts` 의 옛 입력/선택 타입·type guard·legacy map 제거
- ✅ `V2SelectFilter` 의존을 canonical `OptionFilter` 로 이전
- ✅ `rg "V2Input|V2Select|V2InputConfigPanel|V2SelectConfigPanel|components/v2/V2Input|components/v2/V2Select" frontend/lib frontend/components frontend/app frontend/types`
결과는 `V2SelectedItemsDetailInputConfigPanel` substring 매치만 잔존 (별개 컴포넌트)
- ✅ `rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" frontend/lib frontend/components frontend/app frontend/types frontend/styles` — 0건
- ✅ FieldConfig / DataPort 계약 축소 없음. 이번 변경으로 `invyone-component.ts` / `dataPort/runtime.ts` 추가 변경 없음.
### Phase E — 옛 6개 폐기
@@ -345,14 +821,14 @@ const runtimeConfig =
## 4. 위험 영역 / 주의사항
1. **V2Input / V2Select 폐기 전 고유 기능 이식 검증 필수**
- V2Input: numbering API · mask · password · slider · color picker
- V2Select: radio/check/toggle/swap mode · entity FK 검색·auto-fill · option loader (api/code/category/distinct/계층)
1. **옛 입력/선택 고유 기능 이식 검증**
- 이식 완료: numbering API, password/email/tel/url, money/number, radio/check/toggle/swap, entity code-name option, option loader(api/code/category/distinct/계층)
- 남은 항목: mask, slider, color picker, file/image/img 통합
2. **DB 마이그 (Phase D 4단계)**
- 화면 데이터는 `screens` 테이블의 `layout` JSON 안에 통째 저장 (별도 component 테이블 없음 — `screen_components` 같은 이름 X)
- JSON 안 `componentType` / `componentConfig` 변경 SQL 필요 (jsonb_set 또는 string replace)
- 운영 단계 아니라 데이터 마이그 부담 작음 — 단 UPDATE 사용자 승인 필요 (메모리)
2. **DB layout 마이그 금지**
- 새 솔루션 개발 기준이므로 기존 저장 화면 보존은 목표가 아님.
- `screens` / `templates` JSON 안 componentType 을 변환하는 SQL 작성 금지.
- 정리 대상은 코드의 생성/렌더/설정/schema 경로이며, FieldConfig / DataPort 계약 호환을 우선한다.
3. **dev reload 캐시**
- webTypeMapping.ts 변경은 새로 끌어 놓는 컴포넌트만 반영
@@ -379,12 +855,15 @@ frontend/lib/utils/getComponentConfigPanel.tsx ← componentId
frontend/lib/registry/DynamicComponentRenderer.tsx ← 캔버스 렌더 dispatch (fieldType swap 제거됨)
frontend/components/screen/panels/V2PropertiesPanel.tsx ← properties 패널 라우팅
[폐기 완료]
frontend/components/v2/V2Input.tsx ← 삭제 완료
frontend/components/v2/V2Select.tsx ← 삭제 완료
frontend/components/v2/config-panels/V2InputConfigPanel.tsx ← 삭제 완료
frontend/components/v2/config-panels/V2SelectConfigPanel.tsx ← 삭제 완료
[폐기 예정]
frontend/components/v2/V2Input.tsx ← 1286줄
frontend/components/v2/V2Select.tsx ← 1350줄
frontend/lib/registry/components/{date,text,number}-input/ ← 자체 캔버스 컴포넌트 폐기
frontend/lib/registry/components/{select,checkbox,textarea}-basic/
frontend/components/v2/config-panels/V2InputConfigPanel.tsx ← 832줄 (이전 세션 폐기 보류)
[유틸 / API]
frontend/lib/utils/autoGeneration.ts ← AutoGenerationUtils.generateValue (A.5 hook 연결됨)
@@ -397,7 +876,7 @@ frontend/lib/api/numberingRule.ts ← previewNumberi
| 결정 | 이유 |
|---|---|
| Canonical = InvFieldConfigPanel + InputComponent | brumb (kind/type/format) 풍부, FieldConfig spec 정합 |
| Canonical = FieldConfig/DataPort + InputComponent | FieldConfig 가 유일한 필드 규격, DataPort 가 컴포넌트 통신 계약 |
| (가)/(다) 하이브리드 거부 → (나) 통합 추진 | canonical 1안 원칙 / 운영 단계 아님 |
| webTypeMapping → input 점진 변경 | V2Select 고유 기능 이식 후 매핑 변경 (안전) |
| native `<select>` → SingleSelectPicker | OS 기본 dropdown 통일 어려움 + V2Select 풍부 기능 흡수 |
@@ -411,31 +890,18 @@ frontend/lib/api/numberingRule.ts ← previewNumberi
### 우선순위
1. **B.4.5 — SwapPicker** (큰)
- multi + mode=swap
- 양쪽 list (선택 가능 / 선택됨) + 이동 버튼
- V2Select.tsx:530~ 참고
1. **D 후속 — file/image/img → input 통합**
- webTypeMapping 의 file/image/img 경로
- 기존 file-upload / image-widget / v2-media 기능 중 필요한 것만 canonical input 으로 흡수
2. **B.5 — option loader** (중)
- api / code / category / distinct / 계층
- V2Select 의 option 로딩 로직 이식
- apiClient 의존
2. **C 후속 — entity filter runtime** (중)
- `filters` 를 entity/category/code option query 에 runtime 적용
- field/user/static 값 치환 후 API query 전달
3. **Centity 검색팝업 + auto-fill** (큰)
- V2Select.tsx:1007~ 의 entity FK 검색
- 다른 컬럼 auto-fill (조인)
- 현재 InputComponent entity 분기 = placeholder 버튼만
3. **D남은 입력계 폐기**
- mask / slider / color picker 흡수 여부 결정
4. **DV2 폐기** (위 1~3 완료 후)
- webTypeMapping 의 select 계열 (multi/category/entity/code/checkbox/radio/boolean) → input
- webTypeMapping 의 file/image/img → input (file 통합 후)
- `ScreenSettingModal.tsx:2052-2066` 의 옛 컴포넌트 생성 경로 → input
- DB 마이그 — `templates.views` JSON 안 componentType / url 변경 (사용자 승인 필요)
- `V2Input.tsx` / `V2Select.tsx` 파일 삭제
- `registerV2Components.ts` 의 v2-input / v2-select 등록 제거
- `V2InputConfigPanel.tsx` (832줄) 삭제
5. **E — 옛 6개 폐기**
4. **E옛 6개 폐기**
- date-input / text-input / number-input / select-basic / checkbox-basic / textarea-basic 의 캔버스 컴포넌트 (`DateInputComponent.tsx` 등) 폐기
- 6 폴더 자체 삭제 (ScreenSettingModal 의 생성 경로 검증 후)
@@ -443,7 +909,7 @@ frontend/lib/api/numberingRule.ts ← previewNumberi
- ✅ defaultValue 동작 검증 완료 — form-popup 수정 모달에서 default 적용 됨
- 이전에 한 변경들은 form-popup 의 BlockRenderer hijack 으로 동작 검증이 어려웠음. 이제 가능
-Phase B.4.5 / B.5 / C 중 어디부터 진행할지 결정 — 사용자 의도 (사진 확인 가능한 것 우선) 따라
-D 후속 / C 후속 / E 중 어디부터 진행할지 결정 — 사용자 의도 (사진 확인 가능한 것 우선) 따라
### 핵심 사실 (새 세션이 알아야 할 것)
@@ -0,0 +1,215 @@
# Codex Handoff — Input Canonical Migration
날짜: 2026-05-12
브랜치: `gbpark-node`
주 작업 문서: `notes/gbpark/2026-05-08-input-canonical-migration.md`
이 문서는 다른 컴퓨터에서 Codex가 바로 이어받기 위한 요약이다.
---
## 1. 목표
INVYONE Studio 입력 계열을 `FieldConfig / DataPort` 계약을 유지한 채 canonical `input`으로 통합한다.
핵심 원칙:
- `FieldConfig`의 원천은 `frontend/types/invyone-component.ts`다. 설정 패널 UI가 원천이 아니다.
- `DataPort / Connection` 계약은 축소하지 않는다.
- `v2-input`, `v2-select`는 구현체, alias, fallback, schema, DB layout 마이그 대상이 아니다. 제거 대상이다.
- 기존 DB layout JSON 마이그레이션은 하지 않는다. 새 솔루션 개발 기준이다.
- `entity`는 검색 모달이 아니라 참조 테이블의 code-name 옵션 source다.
- 검색 모달이 필요하면 별도 `entity-search-input` 계열에서 처리한다.
---
## 2. 완료된 범위
### V2 input/select 제거
삭제 완료:
- `frontend/lib/registry/components/v2-input/`
- `frontend/lib/registry/components/v2-select/`
- `frontend/components/v2/V2Input.tsx`
- `frontend/components/v2/V2Select.tsx`
- `frontend/components/v2/config-panels/V2InputConfigPanel.tsx`
- `frontend/components/v2/config-panels/V2SelectConfigPanel.tsx`
정리 완료:
- renderer alias / fallback 제거
- config panel alias 제거
- schema/default 제거
- registerV2Components 등록 제거
- V2 demo / V2 renderer 직접 참조 제거
- `types/v2-components.ts`에서 V2Input/V2Select 타입 제거
검증 기준:
```bash
rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" frontend/lib frontend/components frontend/app frontend/types frontend/styles
```
결과는 0건이어야 한다.
### canonical select/entity
추가:
- `frontend/lib/registry/components/input/use-option-loader.ts`
- `frontend/lib/registry/components/input/select-pickers.tsx``SwapPicker`
`useOptionLoader` source:
- `static`
- `code`
- `category`
- `entity`
- `distinct` / `select`
- `api`
- `db`
entity 정책:
- `/entity/{table}/options?value={valueColumn}&label={labelColumn}`
- `config.entityTable/entityValueColumn/entityLabelColumn`
- 또는 `config.ref.table/valueColumn/displayColumn`
- entity modal 재유입 금지
filter runtime:
- `OptionFilter[]`를 runtime에서 `formData` / `userContext`로 치환
- `entity`, `distinct/select`, `db` URL에 `filters=` query append
- `code`, `category`, `api`는 backend/external spec 미정으로 미적용
### canonical file/image/img
추가:
- `frontend/lib/registry/components/input/file-picker.tsx`
변경:
- `InputComponent``case "file"``FilePicker` 사용
- `file/image/img/picture/photo`는 canonical input file로 라우팅
- `webTypeMapping.ts`에서 `file/image/img` 모두 `componentType: "input"`
- `DynamicWebTypeRenderer`의 old FileUpload/ImageWidget 직접 import 제거
- `InteractiveScreenViewerDynamic` master save에서 canonical file 값 업로드 후 id/path로 치환
file 저장 정책:
- `string/string[]`: 이미 저장된 값으로 보고 유지
- `File/File[]`: `uploadFiles` 호출 후 `id ?? server_path ?? server_filename`로 치환
- mixed `(File | string)[]`: string 유지 + File 업로드 결과 합침
- 업로드 실패 시 `File` 객체가 `saveData`에 들어가지 않도록 string만 유지
old file/media 삭제 완료:
- `frontend/lib/registry/components/v2-file-upload/`
- `frontend/lib/registry/components/v2-media/`
- `frontend/lib/registry/components/image-widget/`
- `frontend/lib/registry/components/image-display/`
- `frontend/components/v2/V2Media.tsx`
- `frontend/components/v2/config-panels/V2MediaConfigPanel.tsx`
- `frontend/components/v2/config-panels/V2FileUploadConfigPanel.tsx`
- `frontend/components/screen/widgets/types/ImageWidget.tsx`
- `frontend/lib/registry/components/file-upload/FileUploadComponent.tsx`
- `frontend/lib/registry/components/file-upload/FileUploadRenderer.tsx`
- `frontend/lib/registry/components/file-upload/FileUploadConfigPanel.tsx`
- `frontend/lib/registry/components/file-upload/config.ts`
- `frontend/lib/registry/components/file-upload/README.md`
보존:
- `frontend/lib/registry/components/file-upload/FileViewerModal.tsx`
- `frontend/lib/registry/components/file-upload/FileManagerModal.tsx`
- `frontend/lib/registry/components/file-upload/types.ts`
- `frontend/lib/registry/components/file-upload/index.ts`는 shared viewer/type export shim
검증 기준:
```bash
rg "componentType: \"v2-file-upload\"|file: \"v2-file-upload\"|image: \"v2-file-upload\"|img: \"v2-file-upload\"" frontend/lib frontend/components frontend/app frontend/types
rg "FileUploadComponent|FileUploadRenderer|FileUploadConfigPanel|V2Media\\b|V2MediaConfigPanel|V2FileUpload|ImageWidget|ImageDisplay" frontend/lib frontend/components frontend/app frontend/types
```
첫 번째는 0건이어야 한다. 두 번째는 폐기 주석, `WidgetRenderer``isImageWidget` 문자열, `pop-text` 내부 helper 정도만 남아야 한다.
---
## 3. 현재 known verification
통과:
```bash
git diff --check
git diff --cached --check
rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" frontend/lib frontend/components frontend/app frontend/types frontend/styles
rg "componentType: \"v2-file-upload\"|file: \"v2-file-upload\"|image: \"v2-file-upload\"|img: \"v2-file-upload\"" frontend/lib frontend/components frontend/app frontend/types
rg "EntityPicker|entity-picker|EntitySearchModal" frontend/lib/registry/components/input frontend/components/v2/config-panels/InvFieldConfigPanel.tsx
```
전체 `tsc`는 아직 전역 기존 오류로 실패한다. D.6 삭제로 인한 old file/media `Cannot find module`은 0건이었다.
현재 확인된 기존 module 오류:
```text
@/lib/api/hierarchyColumn
@formkit/auto-animate/react
@/lib/types/screen
```
---
## 4. 다음 작업
### Phase D.7 — canonical input mask / slider / color 흡수
목표:
- 남은 옛 입력 고유 기능 `mask`, `slider`, `color`를 canonical `input`에 흡수한다.
- `v2-input/v2-select` 복구 금지.
- old file/media 복구 금지.
- FieldConfig / DataPort 변경 금지.
구현 방향:
- `slider`
- `type="number" + format="slider"` 또는 `inputType/webType === "slider"`
- `min/max/step` 지원
- `propagate` 흐름 유지
- readonly/disabled 처리
- `color`
- `type="text" + format="color"` 또는 `inputType/webType === "color"`
- native color input + hex text input
- 값은 `#rrggbb` 문자열
- `mask`
- `InputConfig.mask?: string`
- text onChange에서 간단 mask 적용
- `#` 또는 `0` = digit, `A` = alphabet, `*` = any char, 나머지는 literal
수정 후보:
- `frontend/lib/registry/components/input/InputComponent.tsx`
- `frontend/lib/registry/components/input/types.ts`
- `frontend/components/v2/config-panels/InvFieldConfigPanel.tsx`
- `frontend/lib/utils/webTypeMapping.ts`
- `frontend/components/screen/panels/ComponentsPanel.tsx`
- `frontend/lib/registry/components/index.ts`
검증:
```bash
git diff --check
git diff --cached --check
rg "v2-input|v2-select|V2InputRenderer|V2SelectRenderer" frontend/lib frontend/components frontend/app frontend/types frontend/styles
rg "componentType: \"v2-file-upload\"|file: \"v2-file-upload\"|image: \"v2-file-upload\"|img: \"v2-file-upload\"" frontend/lib frontend/components frontend/app frontend/types
rg "EntityPicker|entity-picker|EntitySearchModal" frontend/lib/registry/components/input frontend/components/v2/config-panels/InvFieldConfigPanel.tsx
```
### Phase E — old 6 input components 삭제
D.7 이후 진행.
대상:
- `frontend/lib/registry/components/date-input/`
- `frontend/lib/registry/components/text-input/`
- `frontend/lib/registry/components/number-input/`
- `frontend/lib/registry/components/select-basic/`
- `frontend/lib/registry/components/checkbox-basic/`
- `frontend/lib/registry/components/textarea-basic/`
전제:
- `webTypeMapping`, `ScreenSettingModal`, `ComponentsPanel`, `components/index.ts`, config panel alias가 모두 canonical `input`으로 정리되어야 한다.
- `DateInputComponent.tsx` 기존 타입 오류는 삭제 시 자연 해소될 가능성이 높다.
---
## 5. 금지 사항
- `v2-input`, `v2-select` 문자열 재생성 금지
- `V2InputRenderer`, `V2SelectRenderer` 복구 금지
- `V2Input.tsx`, `V2Select.tsx` 복구 금지
- `EntityPicker` / `EntitySearchModal`을 canonical input에 넣지 말 것
- old file/media 본체 복구 금지
- DB layout JSON 변환 SQL 작성 금지
- FieldConfig / DataPort 계약 축소 금지