# INVYONE Studio Config Panel · Day 2 — HTML V4 풀 매칭 + 디자인 토큰 정리

날짜: 2026-04-28
작업자: gbpark
이전: `notes/gbpark/2026-04-27-invyone-studio-config-panel-cp-migration.md` (Day 1)

---

## 0. 한줄 요약

Day 1 으로 깔린 CP 프리미티브 위에 — **(a) HTML 시안 `panel-input-new.jsx` (V4) 의 4 kinds × 10 types × 32 formats 분류 체계 풀 매칭**, **(b) 카드형 디자인 일체 배제** (외곽 + 내부 active 박스 모두 제거 → 평면 strip + gradient underline + transparent 선택 영역), **(c) Native `<select>` 전면 폐기 → cp 톤 커스텀 popover 로 통일**.

2 commits 후 `origin/gbpark-node` push 완료.

---

## 1. Day 1 회고 (감사 결과)

기존 노트 검토 — 대부분 의도대로 동작. 발견한 정리 후보 (Day 2 에서 일부 처리):

| 항목 | 위치 | Day 2 처리 |
|---|---|---|
| `[InputComponent] propagate` console.log | `lib/registry/components/input/InputComponent.tsx:155` | 유지 (사용자 결정 보류) |
| `bg-amber-100` 디버그 박스 | `V2PropertiesPanel.tsx:1040` (Widget WebType 분기) | 유지 (작업 범위 외) |
| `overflow-x-auto` 컨테이너 (cp-pop 잘림 우려) | `V2PropertiesPanel.tsx:170, 1191` | 유지 (실제 잘림 미관찰) |
| 중복 shadcn `<Separator className="my-2" />` 3개 | `V2PropertiesPanel.tsx:1197 / 1203 / 1286` | **제거 ✓** |
| 우측 패널 상단 "속성 / 1 selected" 헤더 | `ScreenDesigner.tsx:8792~8821` | **제거 ✓** (의미 없음) |
| `frontend/lib/registry/utils/performanceOptimizer.ts` | (사용 안 함) | **삭제 ✓** (별도 커밋) |

---

## 2. CP 프리미티브 — 디자인 토큰 정리

### 2.1 다크 모드 luminance 분리 (`cp.css`)

**문제**: 사이트 기본 `.dark .ide-builder` 의 `--card: 220 6% 9%` / `--background: 5%` → cp 카드와 패널 차이 4%p 로 안 보임.

**해결**: `.dark .inv-right-panel` 스코프에 cp 토큰 명시 override:

```css
.dark .inv-right-panel,
html.dark .inv-right-panel {
  --cp-surface:       hsl(220 6% 17%);   /* 카드/입력칸 */
  --cp-bg-subtle:     hsl(220 6% 11%);   /* 패널 외곽 */
  --cp-surface-hover: hsl(220 6% 24%);
  --cp-border:        hsl(0 0% 100% / 0.16);
  --cp-border-subtle: hsl(0 0% 100% / 0.15);
  --cp-border-strong: hsl(0 0% 100% / 0.28);
  --cp-text-sec:      hsl(0 0% 100% / 0.86);
  --cp-text-muted:    hsl(0 0% 100% / 0.62);
}
```

세 차례 조정 거침: `13/6` → `18/4` (너무 셈) → `15/9` → 최종 `17/11`. 6%p 차이 유지하면서 패널이 순검정이 안 되도록.

### 2.2 섹션 헤더 — gradient underline (`CPSection`)

**진화**: 좌측 2px primary 색바 + 폰트 12.5 (Day 1) → 좌측 색바 + tint bg 풀 (실험) → "거추장스러움" → **gradient underline 만 (확정)**

```jsx
<div style={{
  display: "flex",
  alignItems: "baseline",
  flexWrap: "wrap",
  gap: "2px 8px",
  padding: "0 0 4px",
  marginBottom: 6,
  backgroundImage:
    "linear-gradient(to right, rgba(var(--v5-primary-rgb), 0.55), transparent 75%)",
  backgroundSize: "100% 1px",
  backgroundPosition: "bottom left",
  backgroundRepeat: "no-repeat",
}}>
```

타이틀 텍스트 12px / 700 / `cp-text` 색. 섹션 wrapper marginBottom 8 / marginTop 2. 섹션 wrapper 의 top border 도 제거 (gradient underline 이 분리감 담당).

조정 노브 — 페이드 시작점 `transparent 60~90%`, opacity `0.35~0.7`.

### 2.3 brumb (cp-bar) — 좌·우 transparent 영역, 외곽 박스 없음

**진화**: 카드 (border + bg + radius + shadow) → 좌 색바 + tint bg → primary 10% tint bg + radius 4 → **"좌·우는 자기 영역, 외곽 박스 없음" (확정)**

`.cp-bar` 최종:

```css
.cp-bar {
  padding: 4px 0;
  margin: 2px 0 8px;
  position: relative;
}
```

`.cp-crumb-split` 도 카드 프레임 일체 제거 (`background:transparent / border:none / border-radius:0 / box-shadow:none`).

좌측 `✎ 입력 ▾` 셀 + 가운데 `cp-crumb-divider` 1px + 우측 `Aa 글자 모든 텍스트 ▾` 셀. 셀 hover 시에만 primary tint 16~18%.

### 2.4 통합 팝오버 — kind + type 한번에

**이전**: `openKind` / `openType` 별도 state, 두 개 팝오버
**현재**: 단일 `open` state + 2-column popover

```css
.cp-pop.cp-pop-combined {
  left: 0; right: 0;
  display: grid;
  grid-template-columns: 100px 1fr;
  padding: 0;
  overflow: hidden;
}
.cp-pop-kinds-col {
  background: var(--cp-bg-subtle);
  border-right: 1px solid var(--cp-border-subtle);
}
```

**UX**:
- brumb 어느 쪽 클릭이든 통합 팝오버 펼침
- kind 클릭 → 팝오버 안 닫힘 + types 즉시 갱신 (펼친 채 비교 가능)
- type 클릭 → 선택 + 닫힘
- 외부 클릭 / ESC → 닫힘

팝오버 **항목 평면화** — radius 0 + `border-left: 2px solid transparent` (layout 안정용) + active 시 `border-left-color: primary` + tint bg `rgba(primary, 0.10)`. 컨테이너 radius 4 / shadow 한 단 가볍게.

### 2.5 CPSegment — 외곽 박스 / 내부 활성 박스 둘 다 제거

**이전**: 외곽 회색 카드 + 내부 활성 셀 (작은 박스). 사용자 표현 "카드 안에 카드".
**확정**: 평면 horizontal text + 세로 1px 구분선 (디자이너 mockup 의 "+ 대시보드 · 편집 · 제어" 톤)

```jsx
{i > 0 && <span aria-hidden style={{
  width: 1, height: 12,
  background: "var(--cp-border-subtle)",
  margin: "0 6px",
}} />}
<button style={{
  background: "transparent",
  color: active ? "var(--cp-text)" : "var(--cp-text-muted)",
  fontSize: 11.5,
  fontWeight: active ? 700 : 500,
  padding: "2px 4px",
}}>{label}</button>
```

비활성 hover 시 `var(--cp-text-sec)` 로 살짝 밝아짐.

### 2.6 CPSelect — native `<select>` 전면 교체

**문제**: OS 기본 드롭다운이 cp 톤과 어울리지 않음 (옅은 파랑 active row 등 OS 마음대로).
**해결**: 커스텀 popover 로 교체.

| 측면 | 동작 |
|---|---|
| 트리거 | `<button>` (input 외관 — 28px / chevron / focus glow) |
| 펼침 | absolute popover, top `calc(100%+2px)` / left:0 right:0 / z-60 |
| 항목 | `<button>` per option · radius 0 · `border-left: 2px solid transparent` (active 시 primary) · tint bg active |
| 스크롤 | `maxHeight: 280` + active 자동 `scrollIntoView` |
| 닫힘 | 외부 클릭 / ESC |
| API | **기존 `<option>` children 그대로 호환** + `options` array prop 옵션 + `placeholder` prop |
| optgroup | label 무시하고 안의 option 만 평탄화 |

**호환 핵심** — `parseOptionChildren()` 으로 React children 평탄화하면 기존 callsite 변경 0:

```jsx
<CPSelect value={...} onChange={...}>
  <option value="">선택</option>
  <option value="a">옵션 A</option>
</CPSelect>
```

`firstEmpty` (value="") 옵션 자동으로 placeholder 처리.

---

## 3. V2FieldConfigPanel — HTML V4 풀 매칭

### 3.1 분류 체계 확장

| 영역 | 이전 | 현재 |
|---|---|---|
| Kinds | 2 (입력/선택) | **4** (입력/선택/자동/첨부) |
| Types | 7 | **10** |
| Text formats | 6 | **8** |
| 전체 (kind, type, format) 조합 | ~13 | **32** |

상수 (V2FieldConfigPanel.tsx 상단):

```ts
type Kind = "input" | "choice" | "auto" | "attach";
type Type = "text" | "number" | "money" | "date" | "single" | "multi"
          | "autonum" | "formula" | "audit" | "file";

const KINDS: { id: Kind; name: string; icon: ReactNode }[] = [...];
const TYPES_BY_KIND: Record<Kind, (CPCrumbType & { id: Type })[]> = {...};
const FORMATS_BY_TYPE: Record<Type, CPFormatItem[]> = {...};
const KIND_OF_TYPE: Record<Type, Kind> = {...};
const defaultFormatFor = (type: Type) => FORMATS_BY_TYPE[type][0].id;
```

format 목록 (HTML V4 와 1:1):

| Type | Formats |
|---|---|
| text | free / email / phone / rrn / address / card / url / mask (8) |
| number | int / decimal / percent (3) |
| money | krw / foreign (2) |
| date | date / datetime / time / range (4) |
| single | list / code / entity / status / boolean (5) |
| multi | tags / list / code / entity (4) |
| autonum / formula / audit | 1개씩 (3) |
| file | image / doc / any (3) |

### 3.2 양방향 매핑 — `resolveTriple` / `applyTriple`

**`resolveTriple(config, componentType, metaInputType)` → `{kind, type, format}`** 우선순위:

1. config 에 `kind/type` 명시 시 그대로
2. `autoGeneration.type` 검사 (numbering_rule / current_time-user / formula)
3. componentType (`v2-date` / `v2-file`)
4. choice 분기 (`source` / `multiple` / `fieldType`)
5. money / number / date 분기 (`inputType`)
6. text 분기 — **호환 변환 매핑**: `tel`→`phone`, `biz_no`→`rrn`, `none`→`free`, `currency`→`free`
7. webType 폴백 (email / tel / url / password)
8. default `input.text.free`

**`applyTriple(prev, kind, type, format, ctx)` → 새 config**: 각 `(kind, type, format)` 조합마다 `fieldType` / `source` / `inputType` / `autoGeneration` / `multiple` / `accept` / `step` / `unit` / `tags` / `readonly` 등 적절히 설정. 자동 kind 가 아닐 때 기존 autoGeneration 정리.

### 3.3 본문 디스패처 — `FormatBody`

CPSection ② "유형별 설정" 안에서 `(kind, type, format)` 별로 라우팅:

| 분기 | 컴포넌트 |
|---|---|
| input.text.* | `TextOptions` (8 format 분기) |
| input.number.* | `NumberOptions` (정수/소수/퍼센트 분기 + 천단위/단위) |
| input.money.* | `MoneyOptions` (krw/foreign 분기) |
| input.date.* | `DateOptions` (range 시 maxRangeDays + presets) |
| choice.* + format=list | `SelectOptions` (multi prop) |
| choice.* + format=code | `CategoryOptions` |
| choice.* + format=entity | `EntityOptions` (multi prop) |
| choice.single.status | `StatusOptions` (color label list) |
| choice.single.boolean | `BooleanOptions` (Y/N 라벨 + 표시 형태) |
| choice.multi.tags | `TagsOptions` (구분자 / 최대 / 추천목록) |
| auto.autonum | `NumberingOptions` (기존) |
| auto.formula | `FormulaOptions` (수식 / 결과 형식 / 재계산 시점) |
| auto.audit | `AuditOptions` (이벤트 / 값 채우기) |
| attach.file.* | `FileOptions` (accept / 최대 크기 / 복수 / 이미지일 때 썸네일+해상도) |

**신규 옵션 컴포넌트 11종**: TextOptions(8 format 통합) / NumberOptions / MoneyOptions / DateOptions / StatusOptions / BooleanOptions / TagsOptions / FormulaOptions / AuditOptions / FileOptions. 기존 `SelectOptions` / `EntityOptions` 에는 `multi?: boolean` prop 추가.

### 3.4 가시성 룰

| 영역 | 노출 조건 |
|---|---|
| ① 데이터 소속 | 항상 |
| ② format trigger | `FORMATS_BY_TYPE[type].length > 1` |
| ③ 데이터 필터 | `kind===choice && format ∈ {code, entity, status}` |
| 고급 설정 (CPGroup) | `kind !== auto` (자동 kind 는 자체 옵션이 충분) |

### 3.5 미구현 영역 — UI 노출 + `<Hint>` 표시

UI 는 동작하지만 백엔드 미통합. 사용자가 알 수 있게 명시:

| 영역 | 사유 |
|---|---|
| text.address | juso/카카오 주소 API 미통합 |
| text.card | Luhn 검증 / PCI 마스킹 백엔드 X |
| text.rrn | 사업자번호만 ✓, 주민번호 체크섬·암호화 X |
| money.foreign | 다중 통화 컬럼 X (현재는 통화 코드만 함께 저장) |
| multi.entity | M:N 매핑 테이블 X (JSON 으로 저장 임시) |
| auto.formula | `lib/formula-parser` 안전 파서 미구현 — config 저장만 됨 |
| choice.single.boolean | v2-checkbox 컴포넌트로 동작 위임 |
| attach.file.* | v2-file 컴포넌트로 동작 위임 |

---

## 4. ScreenDesigner / V2PropertiesPanel — 외곽 정리

### 4.1 ScreenDesigner

우측 패널 상단 **"속성 / 1 selected · Inspector" 헤더 블록 제거** (8792~8821). 패널이 첫 섹션 (크기) 헤더로 자연스럽게 시작.

### 4.2 V2PropertiesPanel

**중복 shadcn `<Separator />` 3개 제거**:
- `renderBasicTab()` ↔ `renderDetailTab()` 사이
- 조건부 표시 CPGroup 앞
- 컴포넌트 스타일 CPGroup 앞

CPSection 의 gradient underline + CPGroup 의 bottom border 가 자연스럽게 분리감 담당.

---

## 5. 산출물 — 파일 변경 요약

| 파일 | 변경 |
|---|---|
| `frontend/components/v2/config-panels/_shared/cp/cp.css` | 다크 luminance / cp-bar 평면화 / 통합 popover grid / popover 항목 평면화 |
| `frontend/components/v2/config-panels/_shared/cp/CPPrimitives.tsx` | CPSection gradient underline / CPSegment 평면화 / CPSelect 커스텀 popover 전면 교체 (parseOptionChildren) |
| `frontend/components/v2/config-panels/_shared/cp/CPChrome.tsx` | CPCrumb 통합 팝오버 (kind+type 단일 popover) / CPGroup |
| `frontend/components/v2/config-panels/V2FieldConfigPanel.tsx` | KINDS/TYPES_BY_KIND/FORMATS_BY_TYPE 4×10×32 / resolveTriple+applyTriple / FormatBody 디스패처 / 11개 새 옵션 컴포넌트 / 가시성 룰 |
| `frontend/components/screen/ScreenDesigner.tsx` | "속성 1 selected" 헤더 제거 |
| `frontend/components/screen/panels/V2PropertiesPanel.tsx` | 3개 shadcn Separator 제거 |
| `frontend/lib/registry/utils/performanceOptimizer.ts` | **삭제** (사용 안 함) |

---

## 6. 커밋 / Push

| SHA | 메시지 |
|---|---|
| `29682e5b6` | INVYONE Studio Config Panel — CP 프리미티브 신설 + V2FieldConfigPanel HTML V4 스펙 풀 매칭 |
| `4b31fe1b2` | 사용 안 하는 performanceOptimizer 유틸 제거 |

`origin/gbpark-node` push 완료.

---

## 7. 알려진 이슈 / 다음 검증 필요

### 7.1 V2DateConfigPanel / v2-date 컴포넌트와의 관계

V2FieldConfigPanel 에서 `input.date.*` 노출하지만 실제 `v2-date` 컴포넌트는 `V2DateConfigPanel` 별도 패널. brumb 에서 날짜 type 클릭 시 동일 컴포넌트의 `inputType` 만 바뀌고 컴포넌트 자체는 v2-input 그대로 → DateOptions 패널은 보이지만 캔버스 입력 위젯이 일반 text input 으로 렌더될 가능성.

→ 다음: 컴포넌트 타입 자동 전환 (브럼브에서 날짜/파일/체크박스 클릭 시 `componentType` 자체 변경) UX 도입 검토.

### 7.2 기존 데이터 마이그레이션 안전성

`resolveTriple()` 의 호환 변환이 기존 저장 데이터를 깨지 않게 설계됐지만 실제 DB 의 모든 레코드 검증은 미수행.

→ 다음: 기존 화면 (이미 만들어진 v2-input/v2-select 컴포넌트들) 열어서 brumb 표시가 의도한 (kind, type, format) 와 일치하는지 샘플 검증.

### 7.3 V2InputConfigPanel 처리

`V2InputConfigPanel.tsx` (832줄) 가 registerV2Components.ts 등록은 되어있지만 V2PropertiesPanel 에서 직접 V2FieldConfigPanel 로 매핑되어 사용 안 됨. 폐기 보류.

### 7.4 shadcn `<Select>` 잔존

V2PropertiesPanel 의 라벨 위치 / 굵기 등 select 들이 아직 shadcn 직접 사용. cp 톤 통일하려면 CPSelect 로 교체 필요.

---

## 8. Day 3 후보

1. **컴포넌트 타입 자동 전환** — brumb 에서 `input.date` / `attach.file` / `choice.single.boolean` 클릭 시 v2-date / v2-file / v2-checkbox 로 component type 자체 변경 (V2PropertiesPanel 에 `onComponentTypeSwitch(newType)` prop 추가)
2. **shadcn Select → CPSelect 통일** — V2PropertiesPanel 안의 잔존 shadcn Select 들 교체
3. **V2TableListConfigPanel cp 마이그레이션** — 시안 `panel-table-new` 컨셉 적용
4. **기존 화면 마이그레이션 검증** — 운영 중인 v2-input/v2-select 화면들 샘플 열어서 brumb / 옵션 패널 정상 표시 확인
5. **백엔드 미구현 영역 점진 채움** — formula-parser 안전 파서 / RRN 체크섬·암호화 / juso 주소 API / M:N entity 매핑 테이블 / 다중 통화 / status 색 라벨 렌더링
6. **`V2InputConfigPanel.tsx` / registerV2Components.ts 정리** — 폐기 결정

---

## 9. 관련 파일 맵

```
[디자인 시안 — 진실의 원천]
~/Downloads/ui_kits/app/cli_export/standalone/
├── _shared/config-panel.css
├── _shared/config-panel-fields.jsx        ← CP* 14종 원본
├── panel-input-new.{html,jsx}              ← V4 분류 체계 (4 kinds × 10 types × formats)
├── panel-table-new.{html,jsx}              ← Day 3 후보
└── panel-lookup-new.{html,jsx}             ← Day 3 후보

[프로젝트 — Day 2 산출물]
frontend/components/v2/config-panels/_shared/cp/
├── cp.css                                  ← 다크 luminance / cp-bar 평면 / popover grid
├── CPPrimitives.tsx                        ← CPSelect 커스텀 popover / CPSegment 평면 / CPSection gradient
├── CPChrome.tsx                            ← CPCrumb 통합 팝오버 / CPGroup
├── icons.tsx
└── index.ts

frontend/components/v2/config-panels/
├── V2FieldConfigPanel.tsx                  ← 4 × 10 × 32 + resolveTriple/applyTriple + FormatBody
└── V2InputConfigPanel.tsx                  ← 폐기 보류

frontend/components/screen/
├── ScreenDesigner.tsx                      ← 우측 패널 헤더 제거
└── panels/V2PropertiesPanel.tsx            ← Separator 정리

frontend/lib/registry/utils/
└── performanceOptimizer.ts                 ← 삭제 (이번 커밋)
```
