# INVYONE Studio · ConfigPanel 표준 (cp 시스템)

날짜: 2026-04-28
작성자: gbpark
상태: **확정 — 모든 ConfigPanel 마이그레이션의 기준**

---

## 0. 한줄 요약

INVYONE 빌더의 우측 속성 패널은 **cp 프리미티브 (CP*)** 만 사용한다. shadcn `Select/Input/Label/Switch/Card/Collapsible` 직접 사용 금지. 모든 ConfigPanel 은 V2FieldConfigPanel + V2PropertiesPanel 의 흐름을 따른다.

진실의 원천 (Reference Implementation):

| 파일 | 역할 |
|---|---|
| `frontend/components/v2/config-panels/_shared/cp/CPPrimitives.tsx` | CPSection / CPRow / CPStacked / CPText / CPSelect / CPNumber / CPSwitch / CPSegment / CPColor / CPIconBtn |
| `frontend/components/v2/config-panels/_shared/cp/CPChrome.tsx` | CPHeader / CPTabs / CPGroup / CPBindChip / CPCrumb / CPFormatTrigger |
| `frontend/components/v2/config-panels/_shared/cp/cp.css` | cp-* 스타일 + 변수 + 외곽 패널 통일 룰 |
| `frontend/components/v2/config-panels/_shared/cp/icons.tsx` | CP_ICONS 12종 |
| `frontend/components/v2/config-panels/_shared/cp/index.ts` | barrel export |
| `frontend/components/v2/config-panels/V2FieldConfigPanel.tsx` | **Reference 본체** — 4 kinds × 10 types × 32 formats |
| `frontend/components/screen/panels/V2PropertiesPanel.tsx` | **Reference 외곽** — 외형/조건부/스타일 wrap |
| `frontend/components/v2/ConditionalConfigPanel.tsx` | 조건부 표시 (cp 기반) |
| `frontend/components/screen/StyleEditor.tsx` | 컴포넌트 스타일 (cp 기반) |

---

## 1. cp 프리미티브 인벤토리

| 컴포넌트 | 용도 | 대체된 shadcn |
|---|---|---|
| `CPSection` | 섹션 헤더 + 본문 (gradient underline). always open | `<h3>` + 일반 div |
| `CPGroup` | 펼침/접힘 그룹 (chevron + max-height 트랜지션) | `Collapsible` |
| `CPRow` | 라벨 좌 + 컨트롤 우 (grid `80px 1fr`) | `<Label>` + control 수동 배치 |
| `CPStacked` | 라벨 위 + 컨트롤 아래 | `<Label>` + control 수동 배치 |
| `CPText` | 텍스트 input (focus glow + suffix/prefix slot) | `<Input>` |
| `CPSelect` | 커스텀 popover 셀렉트. **자동 검색 (≥12) + 자동 정렬 (≥8)** + stagger | `<Select>` |
| `CPNumber` | 숫자 input (mono font, suffix) | `<Input type="number">` |
| `CPSwitch` | on/off 토글 (primary glow) | `<Switch>` |
| `CPSegment` | 작은 enum 평면 toolbar (구분선만, 카드 박스 X) | radio group / pill tabs |
| `CPColor` | 컬러 picker + hex input | 자체 ColorPickerWithTransparent (별개) |
| `CPIconBtn` | 작은 아이콘 버튼 (24x24) | `<Button size="icon">` |
| `CPCrumb` | kind ▸ type 헤더 brumb (통합 팝오버) | 자체 |
| `CPFormatTrigger` | 본문 안 [형식 · ▾] 트리거 + 팝오버 | 자체 |
| `CPBindChip` | 데이터 바인딩 표시 (table.column) | 자체 |
| `CPHeader` | 패널 상단 큰 헤더 (icon + title + badge + reset) | 자체 |
| `CPTabs` | 탭 (underline / pill 변형) | shadcn `<Tabs>` |

**모든 import 경로**:
```ts
import {
  CPSection, CPRow, CPStacked, CPGroup,
  CPText, CPSelect, CPNumber, CPSwitch, CPSegment, CPColor, CPIconBtn,
  CPCrumb, CPFormatTrigger, CPBindChip, CPHeader, CPTabs,
  CP_ICONS,
} from "@/components/v2/config-panels/_shared/cp";
```

---

## 2. 패널 흐름 표준

### 2.1 입력 필드형 (V2FieldConfigPanel 패턴)

```
[brumb]                          ← CPCrumb (정체성 — kind ▸ type)
① 데이터 소속  (always open)     ← CPSection — 어디 저장
② 유형별 설정 (always open)      ← CPSection — 어떻게 입력/검증
   └ [형식 · ▾]                  ← CPFormatTrigger
   └ format 별 동적 옵션
③ 데이터 필터  (조건부)           ← CPSection — choice 일 때만
▾ 고급 설정                      ← CPGroup default closed
─────────────────  (외곽 wrap by V2PropertiesPanel)
▾ 외형      (default open)       ← CPGroup
   ├ 크기                        ← CPSection
   ├ 옵션 (필수/읽기/숨김)        ← CPSection
   └ ▾ 라벨                      ← CPGroup nested
▾ 조건부 표시                    ← CPGroup
▾ 컴포넌트 스타일                ← CPGroup
   ├ 테두리                      ← CPSection
   ├ 배경                        ← CPSection
   └ 텍스트                      ← CPSection
```

### 2.2 비입력 컴포넌트 (button, table, stats 등)

```
[brumb 또는 CPHeader]            ← 컴포넌트 정체성
① ~  (컴포넌트별 핵심 1~2개)     ← CPSection always open
▾ 고급 설정                      ← CPGroup
─────────────────
▾ 외형                           ← CPGroup default open
▾ 조건부 표시                    ← CPGroup
▾ 컴포넌트 스타일                ← CPGroup
```

### 2.3 흐름 원칙

1. **위 → 아래 = 자주 만지는 → 가끔 만지는**
2. **정체성 (무엇이냐)** 이 가장 위 (brumb 또는 CPHeader)
3. **데이터/모델** 이 그 다음 (어디 저장 / 어떻게 동작)
4. **외형** 이 후순위 (이미 데이터/모델 정해진 후 미세 조정)
5. **고급/조건/스타일** 이 맨 아래 (가끔)
6. **③ 외형** 안에 라벨/크기/옵션 등 **외형 항목은 한 그룹으로 묶음** — 흩어놓지 말 것

---

## 3. CPSection vs CPGroup 사용 규칙

| | CPSection | CPGroup |
|---|---|---|
| **용도** | 핵심 — 사용자가 거의 항상 봐야 함 | 부가 — 가끔만 펼침 |
| **펼침/접힘** | 항상 펼침 (no toggle) | 사용자 토글 가능 |
| **헤더** | gradient underline (얇은 선) | chevron + bold title + bottom border |
| **default 상태** | 항상 노출 | 명시: `defaultOpen={true|false}` |
| **위계** | 1-depth (안에 CPGroup 가능) | nested 가능하나 권장 안 함 (3-depth 금지) |

**예시**:
- ① 데이터 소속 → CPSection (핵심)
- 고급 설정 → CPGroup default closed (부가)
- 외형 → CPGroup default open (외형은 자주 만지지만 한 묶음으로 접고 싶을 때 있음)
- 외형 안의 라벨 → 안의 nested CPGroup default closed (라벨 편집 가끔)

---

## 4. CPSelect 표준 동작

CPSelect 는 **호출처 변경 없이 자동으로 적절한 동작** 을 한다.

### 4.1 자동 결정

```ts
const useSearch = searchable ?? realOpts.length >= 12;
const useSort = sortable ?? realOpts.length >= 8;
```

| 옵션 수 | 검색 | 정렬 |
|---|---|---|
| < 8 (작은 enum) | X | X — 의미 순서 보존 |
| 8 ~ 11 | X | O |
| ≥ 12 (큰 list) | O | O |

### 4.2 정렬 규칙

1. `label !== value` 인 옵션 (의미있는 라벨) 우선
2. 그 안에서 `Intl.Collator("ko")` — 영어 a~z → 한글 가나다 발음순

### 4.3 검색 동작

- popup 상단에 sticky 검색박스 (`cp-select-search-input`)
- 옵션 label 부분일치 (대소문자 무시)
- 빈 placeholder 옵션은 검색 중 숨김
- 결과 0건: `"…" 결과 없음` 표시
- ESC: 검색어 있으면 비우기, 없으면 popover 닫기

### 4.4 placeholder 옵션 (`<option value="">선택...</option>`)

- 일반 옵션과 시각 차별: italic, font-weight 400, fontSize 11, opacity 0.7, minHeight 22
- 다음에 1px 분리선 (border-subtle)

### 4.5 stagger animation

- 각 옵션 mount 시 `cp-slide-in 0.18s` + `animationDelay: index * 12ms` (cap 18)

### 4.6 opt-in/out

```tsx
<CPSelect searchable={false} sortable={false} ...>
```

`searchable / sortable` prop 명시 시 자동 결정 무시. 의미 순서 보존이 필요하면 `sortable={false}`.

---

## 5. 디자인 토큰

### 5.1 cp-* 변수 (cp.css :root + .dark .inv-right-panel)

```css
--cp-surface         /* 카드/입력칸 background */
--cp-bg-subtle       /* 패널 외곽 / 강조 영역 */
--cp-surface-hover   /* hover 시 옅은 톤 */
--cp-text            /* 본문 텍스트 (거의 흰색/검정) */
--cp-text-sec        /* 라벨 — 약간 약함 (alpha 0.78~0.86) */
--cp-text-muted      /* 캡션/help — 더 약함 (alpha 0.55~0.62) */
--cp-border          /* 외곽 보더 */
--cp-border-subtle   /* 섹션 구분선 (얇음) */
--cp-border-strong   /* 강조 보더 */
```

다크 모드는 `.dark .inv-right-panel` 에서 raw HSL 값으로 명시 override (cascade 끊어짐 방지).

### 5.2 v5 토큰 (primary 색)

```css
var(--v5-primary)              /* primary 색 (preset 따라 변함) */
rgb(var(--v5-primary-rgb))     /* 동일, rgba 용 */
var(--v5-primary-rgb)          /* 숫자 만 */
var(--v5-glow-sm/md/lg)        /* primary 글로우 */
```

### 5.3 폰트 사이즈 스케일

| 용도 | 크기 |
|---|---|
| 본문 input/label | 12 |
| 라벨 (CPRow 좌측) | 11.5 |
| 캡션 / help | 10.5 ~ 11 |
| 작은 mono 코드 | 9.5 ~ 10 |
| 섹션 헤더 (외곽 h3/h4) | 10.5 (uppercase, letter-spacing 0.12em) |
| brumb type name | 12.5 |
| brumb kind name | 11.5 |

### 5.4 spacing

| 용도 | 값 |
|---|---|
| CPRow padding | `5px 0` (vertical) |
| CPRow grid gap | `8px` |
| CPRow grid columns | `minmax(60px, 80px) minmax(0, 1fr)` |
| CPSection marginBottom | `8px` |
| CPGroup children padding | `0 0 12px 10px` (좌측 10px 들여쓰기로 위계) |
| input height | `28px` |
| input font-size | `12px` |
| input border-radius | `6px` |

---

## 6. 외곽 패널 (V2PropertiesPanel) 통합 규칙

### 6.1 wrap 구조

```jsx
<div className="flex h-full flex-col">
  <div className="flex-1 overflow-x-auto overflow-y-auto p-2">
    <div className="space-y-4 text-xs">
      {renderDetailTab()}    {/* 컴포넌트별 본체 (V2FieldConfigPanel 등) */}
      {renderBasicTab()}     {/* 외형 (③ 외형 CPGroup) */}
      {/* ▾ 조건부 표시 */}
      {/* ▾ 컴포넌트 스타일 */}
    </div>
  </div>
</div>
```

### 6.2 자체 헤더 금지

- 컴포넌트별 본체 (`V2FieldConfigPanel` 등) 는 자체 외부 헤더 ("속성 / 1 selected" 같은) 가지지 말 것
- brumb (CPCrumb) 가 정체성을 표시하므로 헤더 redundant

### 6.3 inv-right-panel 클래스

- 우측 패널 `<aside>` 에 `className="inv-right-panel"` 부여 → cp.css 의 글로벌 룰 (input height 28, font-size 12, border 통일 등) 적용

### 6.4 부모-자식 간 헤더 중복 금지

- `CPGroup title="조건부 표시"` 안에 호출되는 `ConditionalConfigPanel` 은 자체 "조건부 표시" 헤더 가지지 말 것
- 안의 첫 row 가 토글 (CPSwitch) 또는 핵심 control 부터 시작

---

## 7. 절대 금지 사항 (★ 위반 시 즉시 수정)

| # | 금지 | 이유 |
|---|---|---|
| 1 | shadcn `Select/Input/Label/Switch/Card/Collapsible` 직접 import | cp 시스템 우회 → 톤/사이즈/border 어긋남 |
| 2 | `backdrop-filter: blur(...)`, `var(--v5-glass)` (메인 화면 이후) | 2026-04-21 정책. 로그인 + builder-ide 만 예외 |
| 3 | 카드형 외곽 (`border + border-radius` 박스) 의 무분별 사용 | 사용자 결정: 패널 영역은 통째 fill, 카드형 X |
| 4 | inline `color: ...` 명시 후 변수 cascade 깨짐 | 라이트/다크 둘 다 raw HSL 또는 cp 변수 + .dark override |
| 5 | 컴포넌트 자체 헤더 (부모 CPGroup 과 중복) | 위계 혼란 |
| 6 | 즉흥 hex/rgb 값 (예: `color: "#abc"`) | 디자인 토큰 위반 — 반드시 cp-* 또는 v5-* 변수 |
| 7 | 작은 enum (3~5개) 에 `sortable` 자동 정렬 | 의미 순서 깨짐 — `sortable={false}` 명시 |
| 8 | CPGroup 3-depth 이상 nesting | 위계 깊어 인지 어려움. 2-depth 까지 |
| 9 | 컴포넌트별 자체 padding wrapper (`<div style={{padding:"0 12px"}}>`) 중첩 | 들여쓰기 누적 → V2FieldConfigPanel 의 outer wrapper 가 padding 담당 |

---

## 8. 마이그레이션 가이드 (기존 패널 → cp)

### 8.1 단계별 절차

```
1) 기존 패널의 import 확인
   - shadcn (Select / Input / Label / Switch / Card / Collapsible) 사용 중이면 마이그레이션 대상

2) 패널 호출 위치 확인
   - V2PropertiesPanel 어느 CPGroup 안에 호출되는지
   - 안에 호출되면 자체 헤더는 제거 (부모가 담당)

3) 흐름 결정
   - 핵심 데이터 영역 (CPSection always open) 식별
   - 부가 영역 (CPGroup) 식별
   - 위 표 §2.1 / §2.2 흐름 따름

4) 마크업 교체
   - <Label> + <Input> → <CPRow label="..."><CPText/></CPRow>
   - <Label> + <Select> → <CPRow label="..."><CPSelect>...</CPSelect></CPRow>
   - <Switch> → <CPSwitch>
   - <Card> 또는 <div className="rounded-lg border ..."> → CPSection 또는 CPGroup
   - <Collapsible> → CPGroup
   - 작은 enum (radio/pill) → CPSegment

5) 검증
   - npx tsc --noEmit (해당 파일 새 에러 0)
   - 빌더에서 컴포넌트 클릭 → 패널 톤이 다른 패널과 일관되는지
   - 다크 모드 / 라이트 모드 둘 다 확인
   - 6개 preset (purple/blue/green/orange/pink/cyan) 색 통일 확인
```

### 8.2 "이미 충족됨" 도주 방지

기존 패널이 이미 비슷해 보인다고 minimal change 하지 말 것. 명시적 cp 프리미티브 import + 마크업 교체가 진행되어야 표준.

검증 grep:
```bash
grep -E "^import.*(Label|Select|Input|Switch|Collapsible|Card)\\s*,?\\s*\\}" 패널.tsx
```
→ 0건이어야 통과 (cp 마이그레이션 완료)

---

## 9. 검증 명령

### 9.1 TypeScript

```bash
cd frontend
npx tsc --noEmit 2>&1 | grep -E "components/v2/config-panels/(_shared|V2[A-Z])" | head
```
→ 새 에러 0 (기존 snake_case/camelCase 에러는 무시)

### 9.2 시각 검증

```bash
# dev server
docker compose -f docker/dev/docker-compose.invyone.yml restart  # 또는
cd frontend && npm run dev
```

브라우저:
1. https://localhost:9772/admin/builder 또는 화면관리 → 템플릿 편집
2. 컴포넌트 클릭 → 우측 패널 cp 톤
3. brumb (있는 경우) ▾ 클릭 → 통합 팝오버 (kind + type)
4. CPSelect 12개+ 옵션 → 검색박스 + 정렬 자동
5. CPGroup ▾ 펼침 → 부드러운 max-height transition
6. 라이트/다크 토글 둘 다 자연
7. preset 변경 → primary glow 색 통일

---

## 10. 컴포넌트 prefix 정책 (★ 2026-04-28 결정)

### 10.1 결정

**모든 새 ConfigPanel / 마이그레이션 대상** = `Inv*` prefix (Invyone 약자)

| 영역 | 정책 |
|---|---|
| **컴포넌트 (TSX)** | `V2*` → `Inv*` rename (cp 마이그레이션과 함께) |
| **CP 프리미티브 (CP*)** | 그대로 유지 (cp = ConfigPanel 의미, 기능적) |
| **CSS prefix (`cp-*`, `v5-*`)** | 그대로 유지 (디자인 시스템 의미) |
| **runtime id (`v2-input`, `v2-select` 등)** | **그대로 유지** — DB / 저장된 화면 데이터 호환 |
| **폴더 (`frontend/components/v2/`)** | 모든 패널 마이그레이션 끝나면 **마지막 단계** 로 한 번에 git mv → `inv/` |

### 10.2 마이그레이션 시 같이 rename

cp 마이그레이션은 이름 변경과 **한 PR/커밋에서 함께** 진행:

1. `V2XxxConfigPanel.tsx` 의 마크업을 cp 프리미티브로 교체
2. 같은 커밋에서 `InvXxxConfigPanel.tsx` 로 rename
3. import 경로 갱신 (`registerV2Components.ts`, `getComponentConfigPanel.ts`, V2PropertiesPanel.tsx 안의 import 등)
4. 검증 grep:
   ```bash
   grep -rn "V2XxxConfigPanel" frontend/  # → 0건이어야 완료
   ```

### 10.3 점진 마이그레이션 — V2*/Inv* 공존 OK

작업 중 일부는 V2*, 일부는 Inv* 가 공존. 마지막 폴더 rename 까지 정상 동작 가능. 단 **이미 마이그레이션된 패널은 V2 잔재 0** 보장.

---

## 11. 다음 마이그레이션 후보 (우선순위)

| 우선 | 현재 파일 | 라인 | 마이그레이션 후 | 비고 |
|---|---|---|---|---|
| 1 | `V2TableListConfigPanel.tsx` | 1497 | `InvTableListConfigPanel.tsx` | 시안 `panel-table-new` 스펙 — 가장 임팩트 큼 |
| 2 | `ColorPickerWithTransparent` | ? | `InvColorPicker` (또는 `CPColor` 통합) | cp 톤 정렬 |
| 3 | `V2DateConfigPanel` 등 type 별 패널 | ? | `InvDateConfigPanel` 등 | V2FieldConfigPanel 통합 vs 별도 유지 결정 |
| 4 | `V2InputConfigPanel.tsx` | 832 | (폐기) | V2FieldConfigPanel 이 흡수 — rm |
| 5 | `V2SelectConfigPanel.tsx` | 847 | (폐기) | rm |
| 6 | `V2FieldConfigPanel.tsx` (Reference) | ~2200 | `InvFieldConfigPanel.tsx` | 본체 rename — 다른 마이그레이션 끝나고 일관성 정리 |
| 7 | `V2PropertiesPanel.tsx` (외곽) | ~1300 | `InvPropertiesPanel.tsx` | 외곽 rename |
| 8 | **마지막** — 폴더 rename | — | `frontend/components/v2/` → `inv/` | git mv + import 일괄 갱신 |

---

## 12. Reference 진실의 원천 우선순위

작업 중 충돌 발생 시 다음 순서로 결정:

1. **이 문서** (cp 패널 표준)
2. **V2FieldConfigPanel.tsx + V2PropertiesPanel.tsx** (Reference Implementation)
3. **CPPrimitives.tsx + CPChrome.tsx + cp.css** (프리미티브 정의)
4. **시안 HTML** (`~/Downloads/ui_kits/app/cli_export/standalone/`)
5. CLAUDE.md (전반 컨벤션)

이 문서가 시안과 충돌하면 — 시안 의도를 반영해 이 문서를 업데이트.

---

## 13. 변경 이력

- **2026-04-27 (Day 1)** — CP 프리미티브 신설, V2FieldConfigPanel/V2PropertiesPanel cp 마이그레이션
- **2026-04-28 (Day 2)** — HTML V4 풀 매칭 (4×10×32), 카드형 폐기, native select 폐기
- **2026-04-28 (Day 3 — 이 문서 작성일)**:
  - CPGroup 펼침 애니메이션 (max-height + opacity)
  - CPSelect 트리거/옵션 인터랙션 (hover/open transition + raw HSL fallback)
  - CPSelect 자동 검색/정렬/stagger
  - placeholder 옵션 시각 차별 (italic + 분리선)
  - 패널 배치 재구성 (정체→데이터→유형→외형→고급)
  - brumb 통합 박스 (좌우 보더 없음, 천장 붙음)
  - ConditionalConfigPanel + StyleEditor cp 마이그레이션
  - 본 표준 문서 작성
  - 컴포넌트 prefix 정책 결정 (V2* → Inv*) — 점진 마이그레이션
