# INVYONE 카드 엔진 최종 스펙 (2026-04-10 확정)

> **이 문서가 진실의 원천이다.** 이전 12-grid / 48-col / 섹션 / business-canvas 분기 모델은 모두 폐기됨. 다른 클로드 세션은 이 문서만 따라가면 된다.

**작성일**: 2026-04-10  
**상태**: 구현 착수 가능 (Phase 1 ~ 3)  
**관련 문서**:
- mockup: `notes/gbpark/2026-04-08-invyone-mockup/` (시각 의도, 살아있음)
- 컴포넌트 규격: `notes/gbpark/2026-04-08-invyone-component-spec.md` (FieldConfig 부분만 살아있음)
- 폐기된 12-grid 노트: `notes/gbpark/2026-04-10-template-model-redesign.md` (참고용 obsolete)

---

## 0. 한 줄 요약

**VEX 자유배치 모델 + 컴포넌트 내부 @container 반응형 + 카드 단위 추상화.**  
12-grid / 48-col / 섹션 / business-canvas 분기는 모두 우회 흔적이고 폐기됨. 단일 자유배치 카드 모델로 통일.

---

## 1. 핵심 개념 — 4가지 용어

| 용어 | 의미 | 예시 |
|---|---|---|
| **Component** | 빌더의 재료. VEX v2-* 컴포넌트 100+개. 자체 사이즈 + 자체 모드 가짐 | v2-table-list, v2-bom-tree, v2-button-primary, v2-search |
| **Template** | 한 화면 청사진. 컴포넌트들의 자유배치 묶음. 목록/등록/수정 3뷰 | 수주관리, 인사정보, BOM관리 |
| **Card** | Template 인스턴스. 대시보드에 배치된 한 개. 위치/크기 가짐 | 영업대시보드의 "수주관리 카드 #c1" |
| **Dashboard** | 카드 컬렉션 = 사이드바 메뉴 항목. 카드 자유 배치 | "영업 현황", "운영 모니터링" |

### 관계도
```
Component (v2-*)               Template                 Card                  Dashboard
─────────────                  ────────                ─────                 ──────────
v2-table-list                  수주관리 ───┐              ┌─ 카드#c1 (영업대시보드, left:50,top:50)
v2-search-widget    ────┐                    │    1:N    ├─ 카드#c5 (임원대시보드, left:200,top:80)
v2-button-primary       ├──→  목록 뷰 (자유) ──→인스턴스화├─ 카드#c9 (본부장, left:100,top:200)
v2-bom-tree             │     등록 뷰 (자유)
v2-aggregation          ├──→  수정 뷰 (자유)
... (100+개)            │
                        └──→  인사정보  ───→  여러 인스턴스
                              BOM관리   ───→  여러 인스턴스
                              ...
```

### 두 단계의 자유배치
1. **개발자 (L1+)**: 빈 캔버스에 **컴포넌트** 자유배치 → Template 완성
2. **사용자 (L0)**: 빈 대시보드에 **Template (=Card)** 자유배치 → 나만의 대시보드

= **같은 자유배치 UX 를 두 레이어에서 사용**. 캔버스에 놓는 것의 종류만 다름.

---

## 2. ★ 반응형 메커니즘 (보장 + 한계 + 백업)

### 작동 원리
캔버스 자유배치 자체로는 반응형이 안 됨. **반응형은 컴포넌트 내부에서 처리**.

```
대시보드 캔버스 (자유배치)
  │
  └─ 카드 #1 (수주관리, 800x500)
      └─ Template = 컴포넌트들의 자유배치
          │
          ├─ v2-table-list (720x400)
          │    │
          │    └─ container-type: inline-size
          │    └─ @container (min-width: 600px) → 테이블 모드
          │    └─ @container (max-width: 599px) → 카드 리스트 모드
          │
          ├─ v2-search-widget (400x60)
          │    └─ @container (min-width: 350px) → 가로 필터
          │    └─ @container (max-width: 349px) → 드롭다운 통합
          │
          └─ ...
```

### 카드 사이즈 변경 시 동작
사용자가 카드를 800x500 → 400x500 으로 드래그 리사이즈하면:
1. 카드 폭이 800px → 400px 로 변함
2. 카드 안의 v2-table-list 폭도 720 → 360 으로 변함 (% 또는 fit)
3. v2-table-list 의 `@container` 가 360px 감지 → 카드 리스트 모드로 자동 전환
4. v2-search-widget 도 마찬가지로 좁은 모드로 전환
5. **사용자는 아무것도 안 했는데 내부가 알아서 재배치됨**

### ✅ 잘 작동하는 케이스 (PC 위주 시나리오)
- 카드 폭 600 → 400 같은 적당한 변화
- 컴포넌트가 자체 모드 로직 가지고 있을 때
- 모니터 해상도 차이 (FHD/2K/4K)
- 대시보드에 카드 4개 배치로 카드가 작아지는 경우

### ❌ 안 되는 케이스 (한계)
1. **컴포넌트가 멍청할 때** — @container 모드 없는 컴포넌트는 그냥 잘림
   - **백업 플랜**: 모든 v2-* 컴포넌트에 최소 wide/narrow 2단계 모드 강제 (Phase 1 마이그레이션)
2. **카드 폭 600 → 100 같은 극심한 변화** — 컴포넌트 자체 최소 폭 미달
   - **백업 플랜**: 카드 min-width: 280px 강제. 그 이하는 collapsed (mini) 모드로 자동 전환
3. **컴포넌트들이 "재배치" 되어야 할 때** — 가로 → 세로 재배치는 자유배치라 안 됨
   - **백업 플랜**: 그런 케이스가 필요하면 컨테이너 컴포넌트 (탭/아코디언) 를 씀. 일반 자유배치는 재배치 안 함
4. **모바일/태블릿 (카드 폭 200px 이하)** — 자유배치는 모바일에 부적합
   - **백업 플랜**: PC 위주 시나리오 (사용자 명시). 모바일은 별도 뷰 (Phase 4 이후)

### 보장 명시
> **이 메커니즘으로 PC 위주 시나리오 (FHD~4K, 카드 폭 280~1920px) 에서는 100% 반응형 작동.**  
> 모든 v2-* 컴포넌트가 @container 기반 wide/narrow 모드를 가져야 한다는 조건 충족 시.  
> 이 조건은 Phase 1 마이그레이션의 핵심 작업.

---

## 3. 데이터 모델 — 최종 타입

### 3.1 위치 (단일 모델)
```typescript
/** 자유배치 위치 (px) — 단일 위치 모델 */
export interface FreePosition {
  left: number;    // px
  top: number;     // px
  width: number;   // px
  height: number;  // px
}
```

### 3.2 컴포넌트 (Template 안의)
```typescript
/** Template 안에 배치된 컴포넌트 한 개 */
export interface TemplateComponent {
  /** 인스턴스 ID */
  id: string;
  
  /** 컴포넌트 종류 — ComponentRegistry 의 ID 참조
   *  예: 'v2-table-list', 'v2-bom-tree', 'v2-button-primary' */
  componentId: string;
  
  /** 캔버스 안에서의 위치 (px 자유배치) */
  position: FreePosition;
  
  /** 컴포넌트별 설정 — 모드/옵션 (그리드 모드, 버튼 액션, 컬럼 정의 등)
   *  ComponentRegistry 의 default_config 를 인스턴스 별로 오버라이드 */
  config: Record<string, any>;
  
  /** 컨테이너 컴포넌트의 자식 (탭, 아코디언 등) */
  children?: TemplateComponent[];
  
  /** 데이터 포트 — 컴포넌트 간 통신 */
  inputs?: DataPort[];
  outputs?: DataPort[];
  
  /** 뷰 트리거 — 이 컴포넌트(예: 등록 버튼)가 다른 뷰를 여는지
   *  자동 생성: 빌더가 등록 버튼을 감지하면 create 뷰 placeholder 생성 */
  viewTrigger?: {
    targetView: 'create' | 'edit' | 'detail';
    action: 'open-modal' | 'navigate';
  };
}
```

### 3.3 뷰 (Template 안의 한 화면)
```typescript
/** Template 의 한 뷰 (목록/등록/수정) */
export interface ViewConfig {
  /** 이 뷰의 컴포넌트들 (자유배치) */
  components: TemplateComponent[];
  
  /** 뷰 표시 방식 */
  layout?: 'card' | 'modal';
  
  /** 모달 사이즈 (modal 일 때) */
  modalSize?: { w: number; h: number };
  
  /** 뷰 캔버스의 기본 사이즈 (디자인 시) */
  designSize?: { w: number; h: number };
}
```

### 3.4 Template (한 화면 청사진)
```typescript
/** 재사용 가능한 화면 청사진 */
export interface Template {
  /** 식별 */
  templateId: string;
  name: string;             // '수주관리'
  icon: string;             // '📋'
  badge: string;            // 'ERP · 영업'
  category: string;         // 'sales'
  description?: string;
  
  /** 카드로 배치될 때 기본 사이즈 */
  defaultSize: { w: number; h: number };
  
  /** 데이터 (선택) */
  primaryTable?: string;    // 'ORDER_MASTER'
  fields?: FieldConfig[];   // 컴포넌트들이 공유
  
  /** ★ 3뷰 — 각 뷰는 독립 자유배치 캔버스 */
  views: {
    list: ViewConfig;       // 카드 본체 (필수)
    create?: ViewConfig;    // 등록 모달 (자동 생성 가능)
    edit?: ViewConfig;      // 수정 모달 (자동 생성 가능)
  };
  
  /** 카드 간 통신 — 다른 카드와 주고받는 데이터 */
  inputs?: DataPortDef[];
  outputs?: DataPortDef[];
  
  /** 메타 */
  companyCode: string;
  version: number;
  status: 'draft' | 'published';
  createdAt: string;
  updatedAt: string;
}
```

### 3.5 Card (대시보드 인스턴스)
```typescript
/** 대시보드에 배치된 카드 한 개 */
export interface Card {
  /** 인스턴스 ID */
  id: string;
  
  /** Template 참조 */
  templateId: string;
  
  /** 대시보드 안에서의 위치 (px 자유배치) */
  position: FreePosition;
  
  /** 접힘 (mini 모드) */
  collapsed: boolean;
  
  /** 인스턴스별 설정 오버라이드 */
  configOverride?: Record<string, any>;
  
  /** 인스턴스별 DataPort 연결 */
  inputs?: DataPort[];
  outputs?: DataPort[];
}
```

### 3.6 Dashboard
```typescript
/** 사이드바 메뉴 항목 = 카드 컬렉션 */
export interface Dashboard {
  /** 식별 */
  id: string;
  name: string;             // '영업 현황'
  icon: string;             // '💰'
  
  /** 카드 자유배치 */
  cards: Card[];
  
  /** 카드 간 데이터 연결 */
  connections: CardConnection[];
  
  /** 메타 */
  companyCode: string;
  ownerId?: string;         // 사용자별 대시보드일 때
  isShared: boolean;        // 공유 대시보드 여부
  createdAt: string;
  updatedAt: string;
}

/** 카드 간 데이터 연결 (DataPort 매칭) */
export interface CardConnection {
  id: string;
  from: { cardId: string; port: string };
  to: { cardId: string; port: string };
}
```

### 3.7 폐기되는 타입 (types/invyone-component.ts 정리)
```typescript
// ❌ 폐기 — 더 이상 사용하지 않음
- GridPosition { col, colSpan, row, rowSpan }
- AbsolutePosition { x, y, w, h } (FreePosition 으로 통합)
- ComponentPosition (union 자체 폐기)
- ResponsiveGridOverride { narrow, normal, wide }
- isGridPosition() / isAbsolutePosition() 가드
- TemplateKind ('business' | 'canvas')
- DEFAULT_COMPONENT_LAYOUTS (12-col 기본값)
- CANVAS_KEYWORDS

// ✅ 유지
- FieldConfig, FieldType, FieldRef, FieldOption (그대로)
- DataPort, DataPortType, Connection (그대로, Card 간 통신용)
- ComponentTypeConfig 패밀리 (선택적, v2-* 통합 시 결정)
```

---

## 4. 빌더 구조

### 4.1 두 빌더 통합
```
폐기:
  frontend/components/builder/*       (12-grid 빌더, 미완성)
  
승격:
  frontend/components/screen/ScreenDesigner_new.tsx
    → 메인 빌더 (자유배치 + VEX v2-* + 격자 옵션)
    → 경로 변경 검토: components/template-builder/* 로 이동?
```

### 4.2 빌더 진입점
```
frontend/app/(main)/admin/builder/page.tsx
  → 현재: 12-grid 빌더 import
  → 변경: ScreenDesigner_new import
```

### 4.3 빌더 핵심 기능 (현재 ScreenDesigner_new 가 이미 가짐)
- ✅ 자유배치 + 컴포넌트 자체 사이즈
- ✅ 격자 옵션 (snapToGrid, showGrid 토글)
- ✅ 그룹화 / 실행취소 / 다시실행
- ✅ 단축키 (T/M/P/S/R/D/E)
- ✅ ComponentRegistry 기반 v2-* 100+ 컴포넌트
- ✅ 좌측 패널 (테이블, 컴포넌트), 우측 패널 (속성, 스타일, 격자)
- ✅ 중앙 캔버스 (자유배치 드롭)

### 4.4 빌더에 추가해야 하는 기능 (Phase 1)
- ⏳ **3뷰 전환** (목록/등록/수정 토글) — useBuilderState.ts:25-27 의 BuilderView 패턴 참고
- ⏳ **Template 저장/불러오기** (toTemplate / fromTemplate) — useBuilderState.ts:279-327 패턴 참고
- ⏳ **자동 뷰 생성** (등록 버튼 추가 시 create 뷰 placeholder 생성)
- ⏳ **DataPort 연결 UI** (컴포넌트 간 시각적 연결)
- ⏳ **카드 미리보기** (실제 대시보드에서 어떻게 보일지)

### 4.5 대시보드 빌더 (Phase 2)
대시보드 빌더도 같은 자유배치 UX:
```
frontend/components/dash/
  DashboardCanvas.tsx       ← 대시보드 자유배치 캔버스 (재작성)
  DashboardCard.tsx          ← Card 컴포넌트 = Template 인스턴스
  DashboardLayout.tsx        ← 셸 (기존 유지)
  TemplateLibraryModal.tsx   ← 라이브러리 모달 (templateRenderers 패턴)
```

mockup 의 `js/04-templates.js` + `js/05-state.js` 패턴을 참고:
- `templateRenderers` → `lib/templates/registry.ts` (Template 정의들)
- `renderCanvas` → `DashboardCanvas.tsx`
- `addCardFromLib` → 라이브러리 모달 + 카드 인스턴스 생성
- `makeDraggable` / `makeResizable` / `applyClamp` → `useCardDrag.ts` / `useCardResize.ts`

---

## 5. VEX v2-* 컴포넌트 마이그레이션 가이드 (★ Phase 1 핵심)

### 5.1 마이그레이션 목표
모든 v2-* 컴포넌트가 다음 3가지를 갖추게:
1. **`container-type: inline-size`** — 부모 폭 감지 가능
2. **@container 모드 분기** — 최소 wide/narrow 2단계
3. **INVYONE FieldConfig 와 호환** — primaryTable + fields 를 받아서 동작
4. **DataPort 인터페이스** — inputs/outputs 명시

### 5.2 마이그레이션 패턴 (v2-table-list 예시)

#### Before
```tsx
// frontend/lib/registry/components/v2-table-list/TableListComponent.tsx
export const TableListWrapper: React.FC<Props> = (props) => {
  return (
    <div className="v2-table-list">
      <table>...</table>
    </div>
  );
};
```

#### After
```tsx
export const TableListWrapper: React.FC<Props> = (props) => {
  return (
    <div className="v2-table-list-container">
      {/* 내부에서 모드 분기 */}
      <div className="v2-table-list-wide">
        <table>...</table>
      </div>
      <div className="v2-table-list-narrow">
        {/* 카드 리스트 모드 */}
        {props.data.map(row => <Card key={row.id}>...</Card>)}
      </div>
    </div>
  );
};
```

```css
/* TableListComponent.css */
.v2-table-list-container {
  container-type: inline-size;
  container-name: v2-table-list;
}

.v2-table-list-wide { display: none; }
.v2-table-list-narrow { display: block; }

@container v2-table-list (min-width: 600px) {
  .v2-table-list-wide { display: block; }
  .v2-table-list-narrow { display: none; }
}
```

### 5.3 컴포넌트별 마이그레이션 우선순위

#### 우선순위 1 — 가장 많이 쓰는 것 (Phase 1)
- v2-table-list (테이블)
- v2-search-widget (검색)
- v2-button-primary (버튼)
- v2-aggregation-widget (KPI)
- v2-card-display (카드 표시)
- v2-input, v2-select, v2-date (입력 필드)
- v2-text-display (텍스트)

#### 우선순위 2 — 도메인 특화 (Phase 2)
- v2-bom-tree, v2-bom-item-editor
- v2-shipping-plan-editor
- v2-pivot-grid
- v2-timeline-scheduler
- v2-process-work-standard
- v2-approval-step

#### 우선순위 3 — 보조 (Phase 3)
- v2-rack-structure
- v2-numbering-rule
- v2-category-manager
- v2-divider-line
- v2-file-upload, v2-media

### 5.4 ComponentRegistry 통합
```typescript
// 각 컴포넌트의 createComponentDefinition 호출에 추가
{
  id: 'v2-table-list',
  ...
  // ★ 추가
  containerQuery: {
    breakpoints: {
      narrow: { maxWidth: 599 },
      wide: { minWidth: 600 },
    },
  },
  // ★ INVYONE 통합
  fieldConfigSupported: true,
  dataPorts: {
    inputs: [
      { name: 'searchParams', type: 'params' },
      { name: 'refreshTrigger', type: 'value' },
    ],
    outputs: [
      { name: 'selectedRow', type: 'row' },
      { name: 'selectedRows', type: 'rows' },
    ],
  },
}
```

---

## 6. 작업 순서 (Phase 1 ~ 3)

### Phase 1 — 토대 정리 (1~2주)
**목표**: 폐기/유지 정리 + 핵심 컴포넌트 마이그레이션

1. **타입 정리** (1일)
   - `types/invyone-component.ts` 에서 GridPosition, TemplateKind, DEFAULT_COMPONENT_LAYOUTS, CANVAS_KEYWORDS 폐기
   - FreePosition, TemplateComponent, ViewConfig 정의 추가
   - Template 인터페이스 수정 (kind 필드 제거)
   - Card, Dashboard 인터페이스 추가

2. **components/builder 폐기** (1일)
   - `frontend/components/builder/*` 전체 삭제 (또는 `_obsolete/` 로 이동)
   - `frontend/app/(main)/admin/builder/page.tsx` 의 import 경로 변경
   - 빌더 진입점이 ScreenDesigner_new 를 가리키게

3. **ScreenDesigner 정리 + 3뷰 추가** (3~4일)
   - `ScreenDesigner_new.tsx` 와 `ScreenDesigner.tsx` 중 하나만 남기기 (new 권장)
   - 3뷰 토글 UI 추가 (목록/등록/수정)
   - useBuilderState 패턴 차용 (toTemplate / fromTemplate)
   - 자동 뷰 생성 (등록 버튼 → create 뷰 placeholder)

4. **v2-* 우선순위 1 마이그레이션** (3~4일)
   - v2-table-list, v2-search-widget, v2-button-primary, v2-aggregation-widget, v2-card-display, v2-input, v2-select, v2-date, v2-text-display
   - 각 컴포넌트에 `container-type` + `@container` 추가
   - wide/narrow 모드 분기

5. **Phase 1 검증**
   - 수주관리 화면을 ScreenDesigner 로 만들기 (PoC)
   - 카드 폭 변경 시 내부 컴포넌트가 모드 전환되는지 확인

### Phase 2 — 대시보드 + 카드 시스템 (2~3주)
**목표**: 자유배치 대시보드 + Card = Template 인스턴스 모델

> ### ★ Phase 2 시작 시 가장 먼저 할 일 — Phase 1 에서 미뤄둔 레거시 타입 정리
>
> Phase 1 Step 5 에서 `DashboardCard.tsx` 가 여전히 아래 타입들을 쓰고 있어
> `@deprecated` 주석만 달고 보존했다. Phase 2 의 `DashboardCard.tsx` 재작성
> 과 함께 이 항목들을 **일괄 제거**한다. 안 하면 영원히 남는다.
>
> **제거 대상** (`frontend/types/invyone-component.ts`):
> - [ ] `GridPosition` 인터페이스
> - [ ] `AbsolutePosition` 인터페이스
> - [ ] `ComponentPosition` 유니언
> - [ ] `ResponsiveGridOverride` 인터페이스
> - [ ] `isGridPosition()` / `isAbsolutePosition()` 타입 가드
> - [ ] `TemplateKind` 유니언
> - [ ] `DEFAULT_COMPONENT_LAYOUTS` 상수
> - [ ] `CANVAS_KEYWORDS` 상수
> - [ ] `Template.kind` 필드 (현재 `kind?: TemplateKind` 로 optional + deprecated)
>
> **대체**: 전부 `FreePosition { left, top, width, height }` + 단일 자유배치 모델
> (§3.1) 로 교체. `DashboardCard.tsx` 재작성 중 사용처를 FreePosition 으로
> 교체하면 이 타입들은 자연스럽게 의존처 0 이 되어 제거 가능.
>
> **검증**: Phase 2 종료 전에 `grep -rn "GridPosition\|AbsolutePosition\|TemplateKind"
> frontend/` 로 잔존 확인.

1. **대시보드 데이터 모델** (1일)
   - Dashboard, Card, CardConnection 인터페이스 구현
   - state 관리 (Zustand 또는 Context)

2. **DashboardCanvas 재작성** (3~4일)
   - 자유배치 카드 캔버스
   - 드래그/리사이즈/clamp (mockup `js/02-canvas.js` 패턴)
   - 카드 추가/삭제/이동/접기

3. **TemplateLibraryModal** (2일)
   - mockup 의 라이브러리 모달 (`index.html` 의 library 패턴)
   - Template 목록 → 카드 추가

4. **카드 ↔ Template 인스턴스화 로직** (2일)
   - templateId 로 Template 조회
   - configOverride 적용
   - 카드 헤더 (아이콘+이름+배지+버튼)

5. **사이드바 동적 대시보드 목록** (2일)
   - mockup 의 `js/05-state.js` 의 sidebar 패턴
   - 대시보드 추가/삭제/이름변경

6. **저장/복원** (1일)
   - 백엔드 API + localStorage 폴백
   - 대시보드/카드/연결 모두 영속화

7. **v2-* 우선순위 2 마이그레이션 시작** (병렬, 1주)
   - 도메인 특화 컴포넌트들 (BOM, 출하, 피벗, 일정 등)

### Phase 3 — 데이터 흐름 + 도메인 확장 (3~4주)
**목표**: DataPort 연결 + 모든 v2-* 컴포넌트 마이그레이션 완료

1. **DataPort 연결 UI** (3일)
   - 빌더에서 컴포넌트 간 시각적 연결
   - Connection 데이터 모델 구현
   - 런타임 이벤트 버스 (mockup 패턴)

2. **카드 간 연결 (대시보드 레벨)** (2일)
   - 다른 Template 의 카드끼리 데이터 주고받기
   - 예: 수주 카드의 selectedRow → BOM 카드의 inputs

3. **v2-* 우선순위 3 마이그레이션 완료** (1~2주, 병렬)
   - 나머지 컴포넌트들

4. **자동 생성/프리셋** (1주)
   - Template 자동 생성 (DB 메타 → 기본 Template)
   - 도메인별 시작 템플릿 (수주관리, 인사정보, 재고관리)

---

## 7. 영향 범위 — 파일별 액션

### 폐기
| 파일/폴더 | 액션 |
|---|---|
| `frontend/components/builder/` (전체) | 삭제 (또는 _obsolete/) |
| `frontend/components/builder/BuilderCanvas.tsx` | 삭제 |
| `frontend/components/builder/BuilderToolbar.tsx` | 삭제 |
| `frontend/components/builder/BuilderBlock.tsx` | 삭제 |
| `frontend/components/builder/BuilderPalette.tsx` | 삭제 |
| `frontend/components/builder/BuilderProps.tsx` | 삭제 |
| `frontend/components/builder/BuilderLayout.tsx` | 삭제 |
| `frontend/components/builder/hooks/useBuilderState.ts` | **참고 후 삭제** (3뷰/toTemplate 패턴 차용) |
| `frontend/components/builder/hooks/gridMetrics.ts` | 삭제 (12-grid 헬퍼) |
| `frontend/components/builder/hooks/useBlockDrag.ts` | 삭제 |
| `frontend/components/builder/props/*` | 삭제 |

### 수정 (12-grid 흔적 제거)
| 파일 | 수정 내용 |
|---|---|
| `frontend/types/invyone-component.ts` | GridPosition, TemplateKind, DEFAULT_COMPONENT_LAYOUTS, CANVAS_KEYWORDS, ResponsiveGridOverride 폐기. FreePosition, TemplateComponent, ViewConfig, Card, Dashboard 추가. Template 에서 kind 필드 제거 |
| `frontend/types/screen.ts` | invyone-component.ts 와 통합 검토. ScreenDefinition → Template 매핑 |
| `frontend/app/(main)/admin/builder/page.tsx` | import 변경 (BuilderLayout → ScreenDesigner_new) |
| `frontend/components/dash/DashboardCanvas.tsx` | 자유배치 카드 캔버스로 재작성 |
| `frontend/components/dash/DashboardCard.tsx` | Card = Template 인스턴스 모델로 재작성 (transform: scale 제거 — 이미 결정) |
| `frontend/components/dash/DashboardLayout.tsx` | 셸 정리 |
| `frontend/styles/dashboard.css` | grid CSS 제거, free positioning CSS |
| `frontend/components/layout/AppLayout.tsx` | 사이드바 동적 대시보드 목록 |

### 승격 (메인 빌더로)
| 파일 | 액션 |
|---|---|
| `frontend/components/screen/ScreenDesigner_new.tsx` | 메인 빌더 승격. 3뷰 + Template 저장/로드 추가 |
| `frontend/components/screen/ScreenDesigner.tsx` | 검토 후 폐기 (구버전) |
| `frontend/components/screen/panels/ComponentsPanel.tsx` | 유지 + INVYONE Template 통합 |
| `frontend/components/screen/panels/PropertiesPanel.tsx` | 유지 |
| `frontend/components/screen/panels/GridPanel.tsx` | 유지 (격자 옵션) |
| `frontend/components/screen/panels/TablesPanel.tsx` | 유지 |
| `frontend/components/screen/DesignerToolbar.tsx` | 유지 + 3뷰 토글 추가 |

### 마이그레이션 (v2-* 100+ 컴포넌트)
| 폴더 | 액션 |
|---|---|
| `frontend/lib/registry/components/v2-*/` (모든 폴더) | @container 추가 + INVYONE Template 통합 + DataPort 인터페이스 추가 |
| `frontend/lib/registry/ComponentRegistry.ts` | INVYONE 모델 통합 (containerQuery, dataPorts 필드 추가) |

### 신규
| 파일 | 액션 |
|---|---|
| `frontend/lib/templates/registry.ts` | Template 정의들 (mockup `js/04-templates.js` 의 templateRenderers 패턴) |
| `frontend/components/templates/HrEmployeeList.tsx` | 인사정보 풀 카드 (mockup 매핑) |
| `frontend/components/templates/SalesOrderList.tsx` | 수주관리 풀 카드 |
| `frontend/components/templates/SalesKpi.tsx` | 매출 KPI 카드 |
| `frontend/components/templates/...` | 기타 도메인 카드들 |
| `frontend/components/dash/TemplateLibraryModal.tsx` | 라이브러리 모달 |
| `frontend/hooks/useCardDrag.ts` | 카드 드래그 (mockup `js/02-canvas.js` 패턴) |
| `frontend/hooks/useCardResize.ts` | 카드 리사이즈 |
| `frontend/store/dashboardStore.ts` | 대시보드 state (Zustand) |

---

## 8. 핵심 코드 스니펫 (구현 참고)

### 8.1 자유배치 캔버스 (DashboardCanvas)
```tsx
// frontend/components/dash/DashboardCanvas.tsx
"use client";

import { useDashboardStore } from "@/store/dashboardStore";
import { DashboardCard } from "./DashboardCard";

export function DashboardCanvas() {
  const dashboard = useDashboardStore(s => s.activeDashboard);
  const updateCardPosition = useDashboardStore(s => s.updateCardPosition);
  
  if (!dashboard) return null;
  
  return (
    <div className="dash-canvas" style={{ position: 'relative', width: '100%', height: '100%' }}>
      {dashboard.cards.length === 0 && (
        <div className="dash-empty">
          <button onClick={openLibrary}>+ 템플릿 추가</button>
        </div>
      )}
      {dashboard.cards.map(card => (
        <DashboardCard
          key={card.id}
          card={card}
          onMove={(pos) => updateCardPosition(card.id, pos)}
        />
      ))}
    </div>
  );
}
```

### 8.2 카드 컴포넌트 (DashboardCard)
```tsx
// frontend/components/dash/DashboardCard.tsx
import { useTemplateRegistry } from "@/lib/templates/registry";
import { useCardDrag } from "@/hooks/useCardDrag";
import { useCardResize } from "@/hooks/useCardResize";

export function DashboardCard({ card, onMove }: { card: Card, onMove: (pos: FreePosition) => void }) {
  const template = useTemplateRegistry(card.templateId);
  const { onDragStart } = useCardDrag(card, onMove);
  const { onResizeStart } = useCardResize(card, onMove);
  
  if (!template) return null;
  
  return (
    <div
      className="dash-card"
      style={{
        position: 'absolute',
        left: card.position.left,
        top: card.position.top,
        width: card.position.width,
        height: card.position.height,
        // ★ 카드 자체가 container — 내부 컴포넌트들이 폭 감지
        containerType: 'inline-size',
        containerName: 'card',
      }}
    >
      <div className="dash-card-head" onMouseDown={onDragStart}>
        <span className="dash-card-icon">{template.icon}</span>
        <span className="dash-card-title">{template.name}</span>
        <span className="dash-card-badge">{template.badge}</span>
        <button onClick={() => toggleCollapse(card.id)}>▼</button>
        <button onClick={() => removeCard(card.id)}>×</button>
      </div>
      
      <div className="dash-card-body">
        {card.collapsed
          ? <TemplateRenderer template={template} view="mini" />
          : <TemplateRenderer template={template} view="list" />
        }
      </div>
      
      <div className="dash-card-resize-handle" onMouseDown={onResizeStart} />
    </div>
  );
}
```

### 8.3 Template 렌더러
```tsx
// frontend/components/templates/TemplateRenderer.tsx
export function TemplateRenderer({ template, view = 'list' }: Props) {
  const viewConfig = template.views[view];
  if (!viewConfig) return null;
  
  return (
    <div className="tpl-canvas" style={{ position: 'relative', width: '100%', height: '100%' }}>
      {viewConfig.components.map(comp => (
        <ComponentRenderer key={comp.id} component={comp} />
      ))}
    </div>
  );
}

function ComponentRenderer({ component }: { component: TemplateComponent }) {
  const def = ComponentRegistry.getComponent(component.componentId);
  if (!def) return null;
  
  const Component = def.component;
  
  return (
    <div
      style={{
        position: 'absolute',
        left: component.position.left,
        top: component.position.top,
        width: component.position.width,
        height: component.position.height,
      }}
    >
      <Component {...component.config} />
    </div>
  );
}
```

### 8.4 v2-* 컴포넌트 @container 패턴
```tsx
// frontend/lib/registry/components/v2-table-list/TableListComponent.tsx
export const TableListWrapper: React.FC<Props> = (props) => {
  return (
    <div className="v2-table-list-container">
      {/* 두 모드를 모두 렌더하고 CSS 로 토글 */}
      <div className="v2-table-list-wide">
        <table>
          <thead>...</thead>
          <tbody>...</tbody>
        </table>
      </div>
      <div className="v2-table-list-narrow">
        {props.data.map(row => (
          <div key={row.id} className="v2-table-list-card">
            {props.fields.map(f => (
              <div key={f.column}>
                <span className="label">{f.label}</span>
                <span className="value">{row[f.column]}</span>
              </div>
            ))}
          </div>
        ))}
      </div>
    </div>
  );
};
```

```css
/* TableListComponent.css */
.v2-table-list-container {
  container-type: inline-size;
  container-name: v2-table-list;
  width: 100%;
  height: 100%;
}

.v2-table-list-wide { display: none; }
.v2-table-list-narrow { display: block; }

@container v2-table-list (min-width: 600px) {
  .v2-table-list-wide { display: block; }
  .v2-table-list-narrow { display: none; }
}
```

### 8.5 Template 저장/불러오기 (useBuilderState 패턴 차용)
```typescript
// frontend/store/builderStore.ts
export const useBuilderStore = create<BuilderState>((set, get) => ({
  // ... 기존 state
  
  toTemplate: (): Template => {
    const s = get();
    return {
      templateId: s.templateId || generateId(),
      name: s.templateName,
      icon: s.icon,
      badge: s.badge,
      category: s.category,
      defaultSize: s.defaultSize,
      primaryTable: s.tableName,
      fields: s.fields,
      views: {
        list: { components: s.views.list, layout: 'card' },
        create: s.views.create.length > 0 ? { components: s.views.create, layout: 'modal' } : undefined,
        edit: s.views.edit.length > 0 ? { components: s.views.edit, layout: 'modal' } : undefined,
      },
      companyCode: '*',
      version: 1,
      status: 'draft',
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    };
  },
  
  fromTemplate: (tpl: Template) => {
    set({
      templateId: tpl.templateId,
      templateName: tpl.name,
      icon: tpl.icon,
      badge: tpl.badge,
      category: tpl.category,
      defaultSize: tpl.defaultSize,
      tableName: tpl.primaryTable,
      fields: tpl.fields ?? [],
      views: {
        list: tpl.views.list.components,
        create: tpl.views.create?.components ?? [],
        edit: tpl.views.edit?.components ?? [],
      },
      currentView: 'list',
    });
  },
}));
```

---

## 9. 핵심 보장 사항 (사용자 약속)

> 사용자가 "반응형 된다" 는 말 믿고 진행하는 결정.  
> 이 보장이 깨지면 백업 플랜으로 회피 가능.

### 보장 1: PC 시나리오 (FHD~4K) 100% 작동
- 모든 주요 v2-* 컴포넌트가 wide/narrow 2단계 모드 가지면
- 카드 폭 280~1920px 범위에서 자동 반응형 작동

### 보장 2: 카드 사이즈 변경 시 내부 자동 반응
- 사용자가 카드를 800x500 → 400x500 으로 드래그 리사이즈
- 카드 내부 v2-table-list 가 @container 로 폭 감지
- 자동으로 카드 리스트 모드 전환
- 사용자는 아무 작업 안 함

### 보장 3: 수십 컴포넌트 화면 가능
- PLM/MES 워크벤치 같은 복잡 화면
- 카드 1개 안에 v2-* 컴포넌트 10~20개 자유 배치 가능
- 또는 카드 5~10개 자유 배치
- 카드 간 데이터 연결 (CardConnection) 으로 통신

### 보장 4: VEX 사용자 학습 곡선 거의 없음
- ScreenDesigner 가 이미 자유배치 + 격자 옵션
- VEX 사용자가 "더 정렬 잘 되는 VEX" 로 인식
- 새로 배울 게 거의 없음

### 한계 (백업 플랜 적용 케이스)
- ❌ 모바일/태블릿 (200px 이하) — Phase 4 이후 별도 모바일 뷰
- ❌ 컴포넌트들이 "재배치" 되어야 할 때 — 컨테이너 컴포넌트 (탭/아코디언) 사용
- ❌ @container 미지원 브라우저 — 모던 브라우저 (Chrome 105+, Safari 16+, Firefox 110+) 만 타겟

---

## 10. 검증 체크리스트 (Phase 1 완료 시)

### 코드 정리
- [ ] `frontend/components/builder/*` 폐기 완료
- [ ] `frontend/types/invyone-component.ts` 정리 (GridPosition 등 제거)
- [ ] FreePosition, TemplateComponent, ViewConfig, Card, Dashboard 타입 추가
- [ ] `frontend/app/(main)/admin/builder/page.tsx` 에서 ScreenDesigner_new 사용

### 빌더
- [ ] ScreenDesigner_new 가 메인 빌더로 동작
- [ ] 3뷰 토글 (목록/등록/수정)
- [ ] Template 저장/불러오기
- [ ] 자동 뷰 생성 (등록 버튼 → create 뷰)

### v2-* 마이그레이션 (우선순위 1)
- [ ] v2-table-list @container 적용
- [ ] v2-search-widget @container 적용
- [ ] v2-button-primary @container 적용
- [ ] v2-aggregation-widget @container 적용
- [ ] v2-card-display @container 적용
- [ ] v2-input, v2-select, v2-date 검증

### PoC
- [ ] 수주관리 화면을 ScreenDesigner 로 만들기
- [ ] Template 저장 → 불러오기
- [ ] 카드 폭 800 → 400 변경 시 v2-table-list 가 카드 리스트로 전환

### 보장 검증
- [ ] 카드 폭 변화에 모든 우선순위 1 컴포넌트가 자동 반응
- [ ] 격자 옵션 (snapToGrid) ON/OFF 정상 동작
- [ ] 그룹화/실행취소 정상 동작

---

## 11. 다른 클로드 세션을 위한 작업 지시

이 문서를 보고 작업할 다른 클로드 세션은:

1. **반드시** 이 문서가 진실의 원천이라고 인지할 것
2. 이전 12-grid / 48-col / 섹션 / business-canvas 분기 모델은 모두 폐기됨
3. `notes/gbpark/2026-04-10-template-model-redesign.md` 는 obsolete (참고만)
4. `notes/gbpark/2026-04-08-invyone-mockup/` 의 mockup 의도를 따라갈 것
5. **반응형은 컴포넌트 내부 @container 로 처리** 가 핵심
6. Phase 1 부터 순서대로 진행
7. 각 Phase 완료 시 검증 체크리스트로 확인
8. 막히면 **반드시 사용자에게 질문**, 추측 구현 금지

### 작업 시작 전 필독
- `notes/gbpark/2026-04-08-invyone-mockup/README.md` (mockup 의도)
- `notes/gbpark/2026-04-08-invyone-mockup/js/04-templates.js` (templateRenderers 패턴)
- `notes/gbpark/2026-04-08-invyone-mockup/js/05-state.js` (state 패턴)
- `frontend/components/screen/ScreenDesigner_new.tsx` (메인 빌더 베이스)
- `frontend/lib/registry/ComponentRegistry.ts` (컴포넌트 레지스트리)

### 작업 시 반드시
- 폐기 결정 사항 다시 도입 금지 (12-grid, 48-col, 섹션 등)
- 컴포넌트가 멍청하면 안 됨 (반드시 @container 모드 가짐)
- 자유배치 px 가 기본 (그리드 셀 강제 금지)
- 카드 = Template 인스턴스 (1:N) 관계 명확히

---

## 끝

이 문서는 INVYONE 카드 엔진의 최종 진실. Phase 1 부터 시작하면 됨.

**작성자 메모**: 사용자가 "반응형 된다"는 약속을 믿고 진행하는 결정이므로, 반응형 메커니즘 (컴포넌트 @container) 의 작동을 Phase 1 PoC 에서 반드시 검증할 것. 실패하면 백업 플랜 (카드 min-width, 모드 강제 등) 적용.
