62 KiB
INVYONE Input 통합 — Canonical 마이그레이션 진행 노트
날짜: 2026-05-07 ~ 2026-05-11
작업자: gbpark
컨텍스트: 인비원스튜디오의 입력 계열을 FieldConfig / DataPort 계약을 지키는 canonical input 으로 통합 중. 설정 패널은 계약을 편집하는 UI일 뿐, 진실의 원천은 frontend/types/invyone-component.ts 의 FieldConfig / DataPort.
0. 핵심 원칙
- INVYONE = VEX 의 2세대 리뉴얼. 운영 단계 아님 → 옛 키 fallback / 호환 부담 X. 깨끗한 canonical 1안.
- FieldConfig 가 유일한 필드 규격이고, DataPort / Connection 이 컴포넌트 통신 계약.
- 옛것이 남아있으면 통합 아님. V2 입력/선택은 구현체·alias·fallback·DB 마이그 대상이 아니라 제거 대상.
- GPT-5.5 (codex:rescue) 와 단계마다 교차 검증.
1. 큰 그림 (목표 형태)
[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개 컴포넌트] → 기능 이식 후 폐기
옛 입력/선택 본체 2개 — 삭제 완료, 필요한 기능은 canonical input 으로 흡수
date-input / text-input / number-input / select-basic / checkbox-basic / textarea-basic 의 자체 캔버스 컴포넌트 6개
2. 완료 작업
Phase 1 — ConfigPanel 통합 (이전 세션 + 일부 본 세션)
- ✅
InvFieldConfigPanel의 brumb (4 kinds × 10 types × 32 formats) 그대로 canonical - ✅
resolveTriple에 옛 6개 컴포넌트 ID → triple default 분기 추가 - ✅ 옛 6개
index.ts— config_panel: InvFieldConfigPanel + default_config 에{kind, type, format}triple 추가 - ✅ 신규
input/index.ts— config_panel: InvFieldConfigPanel + default_config triple - ✅
CONFIG_PANEL_MAP["input"]→ InvFieldConfigPanel
Phase 2 — applyTriple canonical cleanup
- ✅
TYPE_VOLATILE_FIELDS상수 +clearVolatileFields함수 - ✅ text/number/money/date/choice 분기 자기 필드만 set, 잔재 일괄 reset
- ✅ formula
next.computed = prev.computed || ""패치 (분기 순환 시 사용자 수식 보존) - ✅ auto/attach 분기 redundant
next.source = undefined제거
Phase 3 — 캔버스 라우팅 통일
- ✅
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 도
inputcanonical 로 정리했고, Phase D.2 에서 V2 입력/선택 fallback 자체 제거
Phase 4 — InputComponent 외형 통일
- ✅ container 가 input box 역할 (border + radius + bg, padding 0)
- ✅
baseInputStyle에서 자체 border 제거 + transparent (이중 박스 해소) - ✅
inputSlotStyle(flex:1, width/height 100%) wrapper — 모든 type 의 위젯 박스 가득 - ✅ label position: absolute, top: -18 (박스 바깥 위 — V2 스타일)
- ✅ entity outer div / button height 100% + flexShrink:0
- ✅ text 분기에서 format 별 native input type 분기 (password/email/tel/url)
Phase 5 — pickers 통일 (date 계열)
- ✅
SingleDatePicker/DateTimePicker/TimePicker/RangeDatePickerclassName prop 전파 → 자체 border 제거 - ✅
DateTimePicker의 sub-picker (SingleDatePicker + TimePicker) 에 className 전파 - ✅
DateTimePickergap-2 → 0 + 가운데 1px divider - ✅
TimePicker의 shadcnInput→ raw<input>(default class 회피) - ✅
RangeCalendarPopoverclassName prop 받음 + RangeDatePicker 가 sub 에 전달 - ✅
RangeDatePickergap-2 → 0
Phase A.5 — 자동생성 hook
- ✅
InputComponent에 useEffect —autoGeneration.enabled시AutoGenerationUtils.generateValue호출 - ✅ 조건: 디자인 모드 X + 값 비어있을 때만 trigger
- ✅ 처리 type: uuid / current_user / current_time / sequence / random_string / random_number / company_code / department
- ✅
numbering_rule은 별도 (Phase A.6 에서)
Phase B.1 — select-pickers 모듈 시작
- ✅
frontend/lib/registry/components/input/select-pickers.tsx신규 - ✅
SingleSelectPicker— Custom Popover dropdown + 검색 + allowClear + 외부 클릭 닫기 + ESC - ✅ InputComponent select 분기 → SingleSelectPicker 사용 (native
<select>대체) - ✅ webTypeMapping
select / dropdown→input(single dropdown)
Phase B.2 — MultiSelectPicker
- ✅
MultiSelectPicker신규 (select-pickers.tsx) — 체크박스 list + maxSelect 차단 + 라벨 join 트리거 - ✅ InputComponent select 분기에 multi 분기 추가 (
type=multi또는config.multiple시) - ✅ value 정규화 (string / string[] 둘 다 받음)
Phase B.4 — displayMode (mode) 통합
- ✅ 처음 시도:
displayMode신규 prop + UI — 중복 발견 후 폐기 (기존config.mode와 동일) - ✅ 정정: 기존
config.mode사용 (dropdown/combobox/radio/check/tag/toggle 6 가지) - ✅
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/swap/tag
- ✅ TYPE_VOLATILE_FIELDS 에
maxSelect추가 (mode는 기존)
Phase B.4 추가 정리 — 외각 box + 복수 선택 토글
- ✅ picker 4개 wrapper 의
opacity-50제거 (시각 흐릿 해소) - ✅ InputComponent select 분기 disabled 에
isDesignMode빼기 — 디자인 모드에서도 클릭 가능 (시각만) - ✅ container border/bg 분기 —
mode === "radio" || "check"시 외각 box 제거 (자체 visual element 가 표시) - ✅ 고급 설정의 "복수 선택" CPSwitch 제거 — brumb 의
단일/다중이 진실의 원천 (단일/다중 = 저장 형태 차이, UX 토글 아님) - ✅ "최대 개수" 노출 조건 —
multi prop으로 분기 (config.multiple 의존 X) - ✅ input default_size 높이 48 → 30 (사용자 의도)
Phase B.4 의 알려진 이슈 (이후 해결됨)
- ✅
기본 선택값 (— 2026-05-11 해결 (BlockRenderer hijack 버그)config.defaultValue) 동작 안 함 - ⚠️ dropdown 모드인데 박스 없음 사례 (사진 #32). 원인 가설:
config.mode잔재 ("radio"/"check"). 새 컴포넌트 끌어 놓으면 박스 정상
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지원
- 단일 entity:
- ✅ 잘못 추가된 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/V2SelectRendererauto-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대신inputcanonical 생성
검증:
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.tsxcase "entity" —SingleSelectPicker/MultiSelectPicker/CheckboxListPicker/SwapPicker로만 라우팅. 모달 / EntityPicker import 없음useOptionLoader.tssource=entity —/entity/{entityTable}/options?value={...}&label={...}로 fetch. 응답 shape (data[],data.success+data,data.options) 정상 normalize
수정 — fetchUrl dep 누락 (옵션 갱신 안 되는 진짜 원인):
use-option-loader.ts의fetchUrluseMemo 의존성 배열에 다음 6개 추가:config.entityTable/config.entityValueColumn/config.entityLabelColumnconfig.ref?.table/config.ref?.valueColumn/config.ref?.displayColumn
- ref 객체 자체를 dep 으로 넣지 않은 이유: 객체 reference 변동만으로 effect 폭주를 방지하기 위해 내부 키만 추출
디자인 모드 가드:
useOptionLoader는isDesignMode === true시needsFetch = false로 fetch 자체 skipInvFieldConfigPanel의 테이블/컬럼 목록 로드는 별도 경로 (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)헬퍼 — 치환 규칙:columntrim 후 빈 값 → skipvalue_type=static→f.value그대로value_type=field→formData[field_ref], 값 없으면 skipvalue_type=user→userContext[user_field], 값 없으면 skipisNull / isNotNull→ value 없이 통과in / notIn→ 배열 또는"a,b,c"문자열 모두 trim 된 배열로 정규화
- ✅
resolvedFiltersJsonuseMemo — runtime 치환 결과를JSON.stringify로 stable string 화. 결과가 빈 배열이면undefined(URL 에 query 자체 미추가) - ✅
fetchUrluseMemo —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 주석 명시)
- entity:
- ✅
fetchUrldep 배열에resolvedFiltersJson추가 → filter 값 변경 시 URL 재생성 → cache key 자동 신규화 - ✅
resolvedFiltersJson자체의 dep —config.filters,formData,userContext4 키 (객체 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+cancelledflag 유지. 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 0rg "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.createObjectURLpreview thumbnail (cleanup 효과 포함, 메모리 누수 방지) - SSR/Node 경로에서
File전역이 없을 수 있어typeof File !== "undefined"가드 적용 - 그 외 파일은
FileText아이콘 + 파일명 (max-w-160 truncate + tooltip) - 다중 시
maxFiles초과 자르기 + 한도 도달 시+추가버튼 disabled disabled / readonly→lockEdit통합 (선택/삭제/추가 모두 차단)- 같은 파일 재선택 가능하도록 hidden input value 매번 초기화
- props:
- ✅
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=trueformat === "doc"면.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx- 사용자 명시
accept / showPreview가 있으면 그 값 우선 - fallback webType 에
img / picture / photo도 file 로 흡수
- native
- ✅
frontend/lib/utils/webTypeMapping.tsfile / 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.tsxwebType === "file" / "image" / "img" / "picture" / "photo"와props.component?.type === "file"모두 canonicalInputComponent로 라우팅 (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 생성되는지 예시:
// 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.tsxfrontend/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/defaultConfigmismatch 도 함께 정리)
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—FileUploadComponentimport 제거,isFileComponentimport 제거,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/V2FileUploadRendererauto-register import 5개 모두 제거lib/utils/getComponentConfigPanel.tsx—CONFIG_PANEL_MAP의v2-media / file-upload / image-display / image-widgetmapping 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/V2MediaConfigPanelimport + v2-mediaComponentDefinition등록 제거components/v2/V2ComponentRenderer.tsx—isV2Media/V2Mediaimport +if (isV2Media(props)) return <V2Media>분기 제거components/v2/V2ComponentsDemo.tsx—V2Mediaimport + media TabsTrigger + media TabsContent 통째 제거components/v2/index.ts—V2Mediaexport + V2Media 타입 re-export (V2MediaType / V2MediaConfig / V2MediaProps) 제거components/v2/config-panels/index.ts—V2MediaConfigPanelexport 제거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 가 중앙 타입 제거로 바로 깨지지 않도록 로컬 orphanV2MediaProps만 임시 정의. 런타임/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건
- 잔존 매치는 모두 (a) 폴더 내부 자기 자신 정의 (
- ✅
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 하는 shimcomponents/screen/widgets/types/index.ts—ImageWidgetimport / export /getWidgetComponentByName의case "ImageWidget"/getWidgetComponentByWebType의 image/img/picture/photo 분기 /WebTypeComponents의image매핑 모두 제거
잔존 grep 매치 분류 (모두 운영 영향 없음):
- 폐기 안내 주석 / 작업 노트
WidgetRenderer.tsx:41의isImageWidget변수 — widgetType 문자열 (image/img/picture/photo) 매칭만. ImageWidget 본체 import 없이 wrapperpointer-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.tsshim 재작성,widgets/types/index.tsImageWidget 정리)
Phase A.8 — 채번 admin 페이지 + 시퀀스 관리 (★ 별도 트랙)
배경: VEX 는 채번을 캔버스 컴포넌트로 처리 (잘못된 구조). 팀장 요구 — 별도 admin 페이지에서 채번 규칙 + 시퀀스 일원 관리. INVYONE 에는 그 기능 없음 → 신규 작성.
작업 중 발견한 mismatch 들 (운영 전이라 모두 동시 fix):
- URL mismatch — backend Controller
/api/numbering-rule(단수) ↔ frontend/numbering-rules(복수)- fix: backend
@RequestMapping복수로 변경
- fix: backend
- 응답 key mismatch — backend
Map.of("code", ...)↔ frontenddata.generatedCode- fix: backend 4 endpoint (preview/test-preview/allocate/generate) 응답 key 모두
generatedCode로 통일
- fix: backend 4 endpoint (preview/test-preview/allocate/generate) 응답 key 모두
- GREATEST 로직 —
updateCurrentSequenceInRulemapper 가 GREATEST 로 sequence 못 내림- fix: admin 전용
setCurrentSequenceInRuleSQL 신규 (GREATEST 없이 직접 SET). 기존 mapper 는 allocateCode 흐름용으로 유지
- fix: admin 전용
- 두 테이블 ground truth 분리 — 실제 발번은
numbering_rule_sequences(prefix 별),numbering_rules.current_sequence는 표시용- fix: admin 의 reset/update 가 두 테이블 다 처리:
deleteSequencesByRuleId(prefix sequences 비움)setCurrentSequenceInRule(numbering_rules 직접 set)
- 추가:
incrementSequenceForPrefix의 INSERT 분기에서 base 값을numbering_rules.current_sequence + 1로 변경 → admin set 값 N 이 다음 발번 N+1 로 정확히 반영
- fix: admin 의 reset/update 가 두 테이블 다 처리:
- 권한 없음 — PUT /sequence + POST /reset 일반 사용자도 호출 가능
- fix:
@RequestAttribute("role")받아서 SUPER_ADMIN/ADMIN/COMPANY_ADMIN 만 허용. 403 으로 거부
- fix:
- Designer wrong rule 위험 —
NumberingRuleDesignerlockedColumn 시 by-column 조회로 카테고리 분기 규칙에서 다른 rule 열림- fix: 기존
initialConfigprop 활용. mount 시 setCurrentRule + setSelectedColumn 직접 set. lockedColumn effect 에if (initialConfig) return가드
- fix: 기존
- race condition — SequenceManagementPanel 의 저장/리셋 동시 실행
- fix: 단일
mutating: null | "save" | "reset"state 로 통합. 양 버튼 disabled 가mutating !== null
- fix: 단일
신규 / 수정 파일:
- ✅
backend-spring/src/main/java/com/erp/controller/NumberingRuleController.java@RequestMapping단수 → 복수PUT /{ruleId}/sequenceendpoint 신규 (권한 체크 포함)POST /{ruleId}/reset에 권한 체크 추가- preview/allocate/generate 응답 key 통일
isAdminRole헬퍼 추가
- ✅
backend-spring/src/main/java/com/erp/service/NumberingRuleService.javaupdateRuleSequence메서드 신규 (두 테이블 처리)resetSequence도setCurrentSequenceInRule사용으로 변경incrementSequenceForPrefix의 INSERT 분기 base = numbering_rules.current_sequence + 1
- ✅
backend-spring/src/main/resources/mapper/numberingRule.xmlsetCurrentSequenceInRuleSQL 신규 (admin 전용)
- ✅
frontend/lib/api/numberingRule.tsupdateRuleSequence(ruleId, newSequence)함수 신규
- ✅
frontend/components/numbering-rule/SequenceManagementPanel.tsx신규- 현재 시퀀스 + 직접 수정 + reset + 미리보기. mutation lock 적용
- ✅
frontend/components/numbering-rule/NumberingRuleDesigner.tsxinitialConfigprop 활용 effect 추가 (by-column 우회)selectedColumn초기 state 를 lockedColumn 으로
- ✅
frontend/app/(main)/admin/systemMng/numberingRuleList/page.tsx신규- 좌측 규칙 목록 + 우측 ① NumberingRuleDesigner + ② SequenceManagementPanel
- 신규 규칙 CTA → NumberingRuleCreateDialog
- ✅
frontend/components/layout/AdminPageRenderer.tsx/admin/systemMng/numberingRuleList라우팅 등록
남은 / 후속:
- ✅ 사이드바 메뉴 DB INSERT (2026-05-11 완료)
- Flyway:
V017__register_numbering_rule_menu.sql+V018__assign_numbering_rule_menu_to_super_admin.sql - 즉시 운영 DB INSERT 실행: MENU_INFO row 등록 (OBJID=NUMBERING_RULE_LIST, SEQ=8, PARENT=시스템 관리 그룹)
- AUTHORITY_SUB_MENU 매핑: 운영 DB 에 SUPER_ADMIN AUTH_CODE 가 없어 INSERT 0 (다른 systemMng 메뉴도 매핑 없이 동작 중. 무해)
- Flyway:
- ✅ v2-numbering-rule / numbering-rule 캔버스 컴포넌트 폐기 (2026-05-11 완료)
lib/registry/components/numbering-rule/폴더 통째 삭제lib/registry/components/v2-numbering-rule/폴더 통째 삭제components/v2/config-panels/V2NumberingRuleConfigPanel.tsx삭제components/screen/templates/NumberingRuleTemplate.ts삭제 (dead code)lib/registry/components/index.ts의 두 import 제거lib/utils/getComponentConfigPanel.tsx의 두 import 제거ComponentsPanel.tsx의 hidden list 에서 "v2-numbering-rule" / "numbering-rule" 제거
- ⏳ 운영 모드 동작 검증 (backend 재시작 필요 — URL mismatch + 응답 key + sequence 흐름 다 fix 됐는지 확인)
Phase A.7 — 스튜디오 내 채번 규칙 생성 (CTA + Dialog)
사용자 요구: 운영에서 채번 규칙은 admin 페이지에서 사전 등록되어야 NumberingPicker 동작. 스튜디오에서도 새 규칙을 만들 수 있게 — InvFieldConfigPanel 의 채번 옵션에 "새 규칙 만들기" CTA + Modal.
- ✅
NumberingRuleDesigner.tsx—lockedColumn?: { tableName, columnName }prop 추가- 좌측 컬럼 목록 UI hide (조건부 render)
- mount 시 자동
handleSelectColumn호출 - selectedColumn 초기 state 를 lockedColumn 으로 (flash 방지)
- "컬럼을 선택하세요" → lockedColumn 시 "채번 규칙을 불러오는 중..." 로 분기
- ✅
frontend/components/numbering-rule/NumberingRuleCreateDialog.tsx신규 — shadcn Dialog wrapper. NumberingRuleDesigner 를 lockedColumn 으로 띄움. onSave → onCreated callback + 자동 닫기 - ✅
InvFieldConfigPanel.tsx— NumberingOptions 에 "+ 새 규칙 만들기" 버튼 + Dialog 통합rulesRefreshKeystate + numberingRules effect dep 추가- 생성 후 새 ruleId 자동 선택 + rules 목록 refresh
- canonical 위치 통일:
autoGeneration.numberingRuleId(옵션 밖) →autoGeneration.options.numberingRuleId(옵션 안)- 옛 입력 설정 패널도 옵션 안에 저장. autoGeneration.ts.generateValue 도 옵션 안 사용.
- InputComponent 의 NumberingPicker
numberingRuleIdprop 도 옵션 안에서 읽음 → 일관 ★ - 이전 옵션 밖 fallback 제거 (canonical 1안 원칙)
- ✅ Codex 검증 — 2 issue fix
- MED: applyRuleId 에
enabled: true+options.numberingRuleId명시 (저장 시 무효 방지) - LOW: selectedColumn 초기 state 를 lockedColumn 으로 (flash 방지)
- OK 항목 (3·5·6) 무수정
- LOW (4 토큰 컨벤션 raw hsl) — 기능 영향 없음, 후속 정리
- MED: applyRuleId 에
Phase A.6 — numbering API hook (완료)
- ✅
frontend/lib/registry/components/input/numbering-picker.tsx신규- 옛 입력 컴포넌트의 채번 본체 추출 — useState/useRef + 3 useEffect (main / debounce / beforeFormSave) + 렌더 (readonly text vs prefix-input-suffix)
previewNumberingCode(ruleId, formData, manualInputValue)API 사용- ruleId 결정 흐름:
props.numberingRuleId우선 → 없으면by-columnAPI → fallbackgetTableColumns.detailSettings.numberingRuleId ____템플릿: 첫 생성 시 templateRef set + 부모 value, 카테고리 변경 시 manualInputValue 유지 + 새 조합값 onChange- debounce (300ms) 디바운스로 manualInputValue 변경 시 suffix 동적 갱신
- beforeFormSave window listener — 저장 직전 조합값 formData 주입
- ✅ Codex 검증 (1차) — 6 issue 지적 → 전부 fix 적용
- cancellation flag (main + debounce) → race condition 방지
??→||(tableName 폴백, 빈 문자열 통과 방지)originalData || _originalData별칭 보강 (isEditMode)- 카테고리 변경 + userEditedRef 시 onChange 호출 (parent value 누락 fix)
- propRuleId main effect dep 추가 + isDesignMode debounce dep 추가
- inputSlotStyle 에
overflow: hidden + borderRadius: inherit이동 (NumberingPicker 모서리 처리) _numberingRuleIdcallback (EditModal/buttonActions 호환) —onRuleIdResolvedprop 추가- Legacy
inputType="numbering"→ "code" 매핑 추가 (V2 저장 데이터 호환)
- ✅ InputComponent.tsx —
case "code"분기 → NumberingPicker 호출. tableName 5경로 (props/config/component/overrides/screenInfo) + isEditMode 노출 - ✅ 타입체크 통과 (numbering-picker / InputComponent 에서 에러 0건)
Phase B.4.4 — TogglePicker
- ✅
TogglePicker신규 — boolean Y/N 토글 스위치 (truthy 자동 판정: bool/숫자/Y/N/true/false/1/0) - ✅ InputComponent
case "checkbox"분기 —mode=toggle시 TogglePicker, 아니면 기존 단일 체크박스
Phase B.3 — TagPicker
- ✅
TagPicker신규 — chip + 입력 (Enter / 구분자 추가, Backspace / ✕ 제거, maxSelect 차단, 중복 방지) - ✅ InputComponent multi 분기 —
format=tags또는mode=tag시 TagPicker
Phase B.4 추가 정리 (사용자 사진 검증 반영)
- ✅ 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/swap/tag
- ✅
폐기 — 기존displayMode신규 propconfig.mode와 중복이라 정리. mode 만 사용 - ✅ Radio/Checkbox list — flex-col → flex-wrap 으로 변경 (디자인/운영 모드 외형 일관, 박스 사이즈에 맞게 자동 wrap)
- ✅ picker 4개 wrapper 의
opacity-50제거 (시각 흐릿 해소) - ✅ InputComponent select 분기 disabled 에
isDesignMode빼기 — 디자인 모드에서도 클릭 가능
Phase B.5 — CPSelect Portal 도입
- ✅
CPSelect의 dropdown 을 React Portal 로 —position: fixed+getBoundingClientRect()좌표 + scroll/resize 시 닫음 - ✅ ConfigPanel 의
overflow:auto와 무관하게 dropdown 화면 끝까지 보임 (사진 #35 의 가려짐 해소)
Phase B.6 — defaultValue 버그 (중요 발견)
증상: 사용자가 ConfigPanel 에 "기본 선택값: 저는 김민호" 설정해도 운영 (form-popup 수정 모달) 에서 default 안 적용. InputComponent props 의 componentConfig.defaultValue: undefined.
진단 단계 (긴 디버그 흐름):
[Input debug]console.log 추가 → 스튜디오 OK, 운영 undefined 확인[saveTemplate]디버그 → edit 뷰overrides.defaultValue = "저는 김민호"저장 정상[loadTemplate]디버그 → editLegacycomponentConfig.defaultValue = "저는 김민호"load 정상[Input render path]디버그 →pageUrl: "/form-popup"확인, rawComponentConfig.defaultValue = undefined- → form-popup 페이지가 BlockRenderer 사용. 거기서 stripping
진짜 원인: frontend/components/dash/BlockRenderer.tsx:54-57
// BEFORE (버그)
const runtimeConfig =
resolvedColumnName != null
? { ...block.config, defaultValue: resolvedValue } // ← 무조건 덮어씀
: block.config;
→ columnName 있는 컴포넌트는 form data 의 그 컬럼 값 (context.formRow?.[columnName]) 으로 defaultValue 를 무조건 hijack. 신규/빈 row 시 resolvedValue = undefined → 사용자 설정 defaultValue 가 undefined 로 덮임.
Fix:
// AFTER
const runtimeConfig =
resolvedColumnName != null && resolvedValue !== undefined && resolvedValue !== null
? { ...block.config, defaultValue: resolvedValue }
: block.config; // ← formRow 값 없으면 사용자 설정 defaultValue 보존
잘못 들어간 경로 (rollback 완료)
- ❌
GPT 진단 — frontendsaveLayoutV2의 payload shape 와 backendbody.get("layout_data")불일치- 처음 GPT 가
saveLayoutV2payload 를layout_data키로 감싸야 한다 제안 - 사용자 짚음: "그러면 옛 컴포넌트는 어떻게 저장됐냐" — 정확. 모순.
- 저장 자체는 정상이었음. INVYONE 스튜디오는
saveTemplate사용 (template_id 가 있을 때), saveLayoutV2 는 별개 경로 - rollback 완료. 정상 흐름 복원
- 처음 GPT 가
임시 디버그 (잔존 — 정리 가능)
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) +cancelledflag - 계층 (
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삭제 - ✅ 혼선 방지용 주석 정리 — 새 생성 경로는
inputcanonical 이며, 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%
- props:
- ✅
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"—V2SelectedItemsDetailInputConfigPanelsubstring 매치만 잔존 (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— 옛rawTypefallback 분기 제거
검증:
- ✅
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. 진행 중 / 남은 작업
Phase A 잔여
- ✅
A.6 numbering API hook(2026-05-11 완료 — NumberingPicker 신규 + InputComponent 통합)
Phase B 진행
- ✅
B.2 MultiSelectPicker — multi / maxSelect(완료) - ✅
B.3 TagPicker — tags (tagbox)(완료) - ✅
B.4 radio / checkbox / toggle(완료) - ✅
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
- ⏳ 실제 옵션 주입 검증 — 공통코드 / 카테고리 데이터 있는 화면에서 운영 동작 확인 필요
디버그 (해결)
- ✅
— BlockRenderer hijack 버그 (2026-05-11 해결)config.defaultValue동작 안 함
Phase C — entity
- ✅ 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 계열 (category/entity/code/checkbox/radio/boolean) → input
- ⏳ webTypeMapping 의 file/image/img → input (file 통합 후)
- ✅
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의존을 canonicalOptionFilter로 이전 - ✅
rg "V2Input|V2Select|V2InputConfigPanel|V2SelectConfigPanel|components/v2/V2Input|components/v2/V2Select" frontend/lib frontend/components frontend/app frontend/types결과는V2SelectedItemsDetailInputConfigPanelsubstring 매치만 잔존 (별개 컴포넌트) - ✅
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개 폐기
- ⏳ date-input / text-input / number-input / select-basic / checkbox-basic / textarea-basic 의 캔버스 컴포넌트 (
DateInputComponent.tsx등) 폐기 - ⏳ 6 폴더 자체 삭제 (ScreenSettingModal 의 생성 경로 검증 후)
- ⏳ 부수 효과 —
DateInputComponent.tsx:214의 옛 TS 에러 자연 해소
4. 위험 영역 / 주의사항
-
옛 입력/선택 고유 기능 이식 검증
- 이식 완료: 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 통합
-
DB layout 마이그 금지
- 새 솔루션 개발 기준이므로 기존 저장 화면 보존은 목표가 아님.
screens/templatesJSON 안 옛 componentType 을 변환하는 SQL 작성 금지.- 정리 대상은 코드의 생성/렌더/설정/schema 경로이며, FieldConfig / DataPort 계약 호환을 우선한다.
-
dev reload 캐시
- webTypeMapping.ts 변경은 새로 끌어 놓는 컴포넌트만 반영
- 캐시 안 잡히면 hard reload (Cmd+Shift+R)
-
canonical pattern — TYPE_VOLATILE_FIELDS
- 새 type 추가 시 잔재 필드도 같이 등록할 것 (안 그러면 분기 간 잔재 → 옛 컴포넌트 분기 트리거 위험)
5. 관련 파일 맵
[Canonical (목표)]
frontend/components/v2/config-panels/InvFieldConfigPanel.tsx ← 단일 ConfigPanel (brumb)
frontend/lib/registry/components/input/InputComponent.tsx ← 단일 캔버스 컴포넌트
frontend/lib/registry/components/input/pickers.tsx ← date/datetime/time/daterange picker
frontend/lib/registry/components/input/select-pickers.tsx ← select picker (B.1 신규)
frontend/lib/registry/components/input/index.ts ← input 등록 (config_panel: InvFieldConfigPanel)
[라우팅]
frontend/lib/utils/webTypeMapping.ts ← web_type → componentType
frontend/lib/utils/getComponentConfigPanel.tsx ← componentId → ConfigPanel
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/lib/registry/components/{date,text,number}-input/ ← 자체 캔버스 컴포넌트 폐기
frontend/lib/registry/components/{select,checkbox,textarea}-basic/
[유틸 / API]
frontend/lib/utils/autoGeneration.ts ← AutoGenerationUtils.generateValue (A.5 hook 연결됨)
frontend/lib/api/numberingRule.ts ← previewNumberingCode (A.6 이식 예정)
6. 핵심 의사결정 기록
| 결정 | 이유 |
|---|---|
| Canonical = FieldConfig/DataPort + InputComponent | FieldConfig 가 유일한 필드 규격, DataPort 가 컴포넌트 통신 계약 |
| (가)/(다) 하이브리드 거부 → (나) 통합 추진 | canonical 1안 원칙 / 운영 단계 아님 |
| webTypeMapping → input 점진 변경 | V2Select 고유 기능 이식 후 매핑 변경 (안전) |
native <select> → SingleSelectPicker |
OS 기본 dropdown 통일 어려움 + V2Select 풍부 기능 흡수 |
TYPE_VOLATILE_FIELDS reset 패턴 |
명시 cleanup 보다 유지보수성 + 새 type 자동 일관 |
| label position: absolute (박스 바깥) | V2 스타일 / 사용자 의도 |
| 옛 데이터 마이그 X | 운영 단계 아님 (사용자 명시) |
7. 다음 세션 진입점
우선순위
-
D 후속 — file/image/img → input 통합
- webTypeMapping 의 file/image/img 경로
- 기존 file-upload / image-widget / v2-media 기능 중 필요한 것만 canonical input 으로 흡수
-
C 후속 — entity filter runtime (중)
filters를 entity/category/code option query 에 runtime 적용- field/user/static 값 치환 후 API query 전달
-
D — 남은 입력계 폐기
- mask / slider / color picker 흡수 여부 결정
-
E — 옛 6개 폐기
- date-input / text-input / number-input / select-basic / checkbox-basic / textarea-basic 의 캔버스 컴포넌트 (
DateInputComponent.tsx등) 폐기 - 6 폴더 자체 삭제 (ScreenSettingModal 의 생성 경로 검증 후)
- date-input / text-input / number-input / select-basic / checkbox-basic / textarea-basic 의 캔버스 컴포넌트 (
새 세션 진입 전 준비
- ✅ defaultValue 동작 검증 완료 — form-popup 수정 모달에서 default 적용 됨
- 이전에 한 변경들은 form-popup 의 BlockRenderer hijack 으로 동작 검증이 어려웠음. 이제 가능
- ⏳ D 후속 / C 후속 / E 중 어디부터 진행할지 결정 — 사용자 의도 (사진 확인 가능한 것 우선) 따라
핵심 사실 (새 세션이 알아야 할 것)
- INVYONE 스튜디오 = templates 모드 (
template_id가 있는 경우saveTemplate호출,saveLayoutV2아님) - 운영 모드 form-popup (
/form-popup?templateId=...) 가getTemplateInfo→ PopupTemplateRenderer → BlockRenderer → InputComponent 경로 - edit 뷰 컴포넌트 가 운영에서 보임 (수정 모달)
- BlockRenderer.tsx:54-57 의 runtimeConfig 가 columnName 기반 defaultValue hijack — fix 적용됨
- 임시 console.log 모두 제거됨 (saveTemplate payload 의 일반 진단 로그만 1줄 유지)
Closing (2026-05-13)
입력 canonical 마이그레이션은 본 phase 시리즈로 종결됨.
- 단일 진실의 원천:
frontend/lib/registry/components/input/InputComponent.tsx(canonical input) - 단일 ConfigPanel:
frontend/components/v2/config-panels/InvFieldConfigPanel.tsx(id:"input") - 삭제된 폴더 11개: 옛 입력 6종 + slider-basic + radio-basic + toggle-switch + test-input +
category-manager.tsxorphan - 보존 explicit/domain 7종: entity-search-input, autocomplete-search-input, selected-items-detail-input, domain/v2-location-swap-selector, v2-category-manager, category-manager/, mail-recipient-selector
- 병합 커밋:
7bd08dcf9 refactor(components): consolidate canonical input cleanup
상세 phase 별 작업과 검증 명령은 notes/gbpark/2026-05-12-codex-handoff-input-canonical.md §6 참고.