# DataPort / FieldConfig 시스템 감사 (2026-04-27)

Claude(Opus 4.7) 1차 분석 + Codex(GPT) 교차검증 + 사용자 정정 반영.

> ⚠️ **2026-04-27 정정**: 초판에서 "DataPort 제거" 를 권고했으나 잘못된 결론이었음. DataPort 는
> INVYONE 컴포넌트 규격 v1.0 의 핵심 차별화 요소(vex 대비 강점)로 새로 도입한 통신 프로토콜이며,
> 인프라가 먼저 깔린 후 컴포넌트 어댑터/빌더 UI 가 따라붙는 단계임. 현재 props 콜백은 활성화 전
> 임시 호환 레이어. 본 문서는 정정 후 버전.

---

## TL;DR

- **DataPort 는 INVYONE 의 핵심 통신 규격.** 인프라(타입/Bus/Runtime/메타/DB컬럼) 깔린 상태, 활성화 대기 중.
- **현재 props 콜백 + context drilling 은 임시 호환 레이어.** 콜백 시그니처가 DataPort 포트와 1:1 매칭되게 설계됨.
- **다음 단계 = 컴포넌트 publish/subscribe 어댑터 + 빌더 시각 연결 편집 UI + `BlockRenderer` 에서 `setupConnections` 호출**.
- **FieldConfig 정상 작동.** 단 `v2-table-list` 레거시 `config.columns` fallback 1곳에서 단일 진실 원천 원칙 위반 — 정리 가능.

---

## 1. 설계 의도 (진실의 원천)

`notes/gbpark/2026-04-08-invyone-component-spec.md` §4 / §7 표:

| | vex | invyone |
|---|---|---|
| 컴포넌트 간 통신 | 자체 구현, 컴포넌트마다 다름 | **DataPort 표준 프로토콜** |
| 데이터 전달 | 컴포넌트별 커스텀 이벤트 | **output → input 자동 매칭** |

`notes/gbpark/2026-04-09-invyone-architecture.md` §1.2:
> 컴포넌트끼리 직접 참조하지 않는다. 대신 표준화된 DataPort로 데이터를 주고받는다.

`notes/gbpark/2026-04-10-phase2-fieldconfig-components.md` (FcTable/FcForm/FcSearch props 주석):
- `onRowSelect` ← // selectedRow(row)
- `onRowsSelect` ← // selectedRows(rows)
- `onSearch` ← // searchParams(params)
- `onSubmit` ← // formData(row), `onSaved` ← // savedRow(row)
- `loadRow` ← // DataPort 입력

→ **현재 콜백 시그니처가 이미 DataPort 포트 이름과 1:1 매칭되도록 설계됨**. props↔DataPort 전환이 어댑터 한 층으로 가능하게 만들어둔 구조.

---

## 2. 현재 구현 상태

| 레이어 | 상태 | 위치 |
|---|---|---|
| 타입 (`DataPort`/`Connection`/`DataPortType`/`ReservedDataPortName`/`DataPortDef`/`CardConnection`) | ✅ 정의됨 | `frontend/types/invyone-component.ts:240~306, 692~693, 898~905, 939~946` |
| Pub/Sub 버스 | ✅ 구현됨 | `frontend/lib/dataPort/DataPortBus.ts` |
| Connection → Bus 브리지 (`setupConnections`) | ✅ 구현됨 | `frontend/lib/dataPort/runtime.ts` |
| 컴포넌트 메타 (`ComponentDefinition.dataPorts: { inputs, outputs }`) | ✅ 9개 등록 | `v2-table-list, input, v2-button-primary, v2-input, search, table, button, v2-table-search-widget, stats` |
| Template DB 컬럼 (`connections jsonb`) | ✅ 생성·저장 경로 | `templateAdapter.ts:93,115` |
| 컴포넌트 publish/subscribe 어댑터 | ❌ 미구현 | (다음 단계) |
| 빌더 시각 연결 편집 UI | ❌ 미구현 | (다음 단계) |
| `BlockRenderer` 에서 `setupConnections` 호출 + cleanup | ❌ 미구현 | (다음 단계) |
| Template.connections 인자를 `saveTemplate` 호출처가 전달 | ❌ 미구현 | `ScreenDesigner.tsx:2797,6757`, `builder/page.tsx:359` |

**현재 작동하는 임시 통신 패턴 (DataPort 활성화 전 1세대):**

```
DashboardCard (state hub)
  ├─ state: searchParams, selectedRow, formRow, page, pageSize
  ├─ handlers: handleSearch, handleRowSelect, handleAdd, handleEdit, handleDelete
  └─ TemplateRenderer({ template, context })
      context.{onSearch, onRowSelect, onAdd, onFormSubmit, ...}  ← DataPort 와 1:1 매칭되는 콜백
      └─ BlockRenderer  ← 콜백을 props 로 펼쳐서 컴포넌트에 전달
          └─ 각 v2-* / fc-* 컴포넌트 (props.onSearch, props.onRowSelect, ...)
```

이 구조가 **DataPort 로 전환되었을 때**:

```
DashboardCard
  ├─ DataPortBus 인스턴스 (화면별)
  └─ TemplateRenderer
      └─ BlockRenderer
          ├─ setupConnections(template.connections, bus)  ← 마운트 시
          ├─ cleanup() ← 언마운트 시
          └─ 각 컴포넌트
              ├─ outputs 발생 시 bus.publish(`${id}.${portName}`, value)
              └─ inputs 채널 bus.subscribe(...)
```

콜백 props 와 DataPort 는 **공존 가능**한 구조. 어댑터 한 층(예: `usePortPublish(componentId, portName)` 훅)으로 양쪽 호환 가능.

---

## 3. FieldConfig 경로 — 정상 작동

| 위치 | 사용 |
|---|---|
| `FcTable.tsx:29`, `FcForm.tsx:16`, `FcSearch.tsx:16` | `FieldConfig[]` 직접 |
| `FieldRenderer.tsx:30` | `FieldConfig.type` → 위젯 매핑 (10종) |
| `lib/fieldConfig/adapters.ts` | `FieldConfig` → 레거시 columns/formFields/searchFields 변환 (호환 레이어) |
| **`v2-table-list/TableListContainerWrapper.tsx:55,70`** | **fallback: `props.fields` 부재 시 `config.columns` 사용 ← 단일 진실 원천 원칙 위반** |

`TableListComponent.tsx:509,1277` 이 `config.columns` 직접 소비 → 이 경로가 유일하게 FieldConfig 원칙 깸. 빌더에서 columns→FieldConfig 정규화 후 fallback 제거 필요.

---

## 4. 모델 이중화 — TemplateComponent vs BlockV2

`invyone-component.ts` 안에 컴포넌트 모델 3개 공존:

| 모델 | 위치 | 실제 사용 |
|---|---|---|
| `Component` (§2) | line 186 | 자체 주석에 "사실상 폐기, 즉시 삭제 가능" — 정리 가능 |
| `TemplateComponent` (§6) | line 804 | **빌더(`ScreenDesigner.tsx:122,2988`, `TemplatesPanel`)에서 사용 중** |
| `BlockV2` (§7) | line 998 | **런타임 진실** — `TemplateRenderer`/`BlockRenderer` 가 받는 모델 |

빌더 → `templateAdapter.saveTemplate` → `convertLegacyToV2` → DB 저장 → `ensureV2Views` → BlockV2 처리.
이중화는 의도된 구조(빌더 모델 vs 런타임 모델 분리). 어댑터로 정합성 유지. **제거 대상 아님**.

---

## 5. 권고

### A) DataPort 인프라 유지 (절대 건드리지 말 것)
타입/Bus/Runtime/메타/DB컬럼 모두 의도해서 깐 인프라. 아직 활성화되지 않은 상태일 뿐.

### B) DataPort 활성화 작업 순서 (다음 단계)

1. **`BlockRenderer` (또는 그 상위 `TemplateRenderer`/`DashboardCard`) 에 Bus 라이프사이클**
   - 화면별 `new DataPortBus()` 또는 `defaultDataPortBus` 사용
   - `useEffect` 로 `setupConnections(template.connections, bus)` 호출 / cleanup
   - React Context 로 자식 컴포넌트에 bus 전달

2. **컴포넌트 어댑터 훅** (props↔Bus 양쪽 호환)
   ```ts
   // 예
   function usePortPublish(componentId: string, portName: string) {
     const bus = useDataPortBus();
     return useCallback((value: unknown) => {
       bus.publish(DataPortBus.channelOf(componentId, portName), value);
     }, [bus, componentId, portName]);
   }
   ```
   기존 `onRowSelect` 콜백 호출 시 동시에 `publish('selectedRow', row)` 도 호출하도록 어댑터 추가. 점진 전환.

3. **빌더에 시각 연결 편집 UI**
   - 컴포넌트 박스에 `dataPorts.outputs/inputs` 메타 기반으로 포트 핸들 자동 표시
   - 드래그로 from.output → to.input 연결 → `Template.connections` 누적
   - `PortHandle.tsx` 가 이미 존재 (제어 모드 노드 에디터용으로) — 패턴 재사용 가능

4. **`templateAdapter.saveTemplate` 호출처에 connections 전달**
   - `ScreenDesigner.tsx:2797,6757`, `builder/page.tsx:359` 가 빌더 상태의 connections 를 인자로 넘기게

### C) DataPort 와 별개로 정리 가능한 항목

- `Component` (§2 invyone-component.ts:186) 인터페이스 — 자체 주석에 폐기 명시. 참조 0건이면 삭제
- `TableListContainerWrapper.tsx:55,70` 의 `config.columns` fallback — 빌더에서 columns→FieldConfig 정규화 후 제거. FieldConfig 단일 원천 회복

### D) 손대지 말 것

- `lib/dataPort/` 전체
- 각 컴포넌트 definition 의 `dataPorts: { ... }` 선언
- `Template.connections`, `Card.inputs/outputs`, `Dashboard.connections` (CardConnection[]) 필드
- `templateAdapter.ts` 의 connections 직렬화
- `TemplateComponent` (§6) — 빌더 모델로 사용 중

---

## 부록: 검증에 사용한 grep

```bash
# DataPort 활성화 경로 (현재 0건 — 다음 단계에서 채워짐)
grep -rn "setupConnections\|defaultDataPortBus\|new DataPortBus\|bus\.publish\|bus\.subscribe" \
  --include="*.ts" --include="*.tsx" frontend/

# FieldConfig 사용처
grep -rn "FieldConfig" --include="*.ts" --include="*.tsx" frontend/

# dataPorts 메타 등록
grep -rn "dataPorts\b" --include="*.ts" --include="*.tsx" frontend/
```

---

## 부록: 초판의 잘못된 결론

초판: "DataPort 전체 제거" 권고. 호출처 0건 = dead code 라는 단순 추론에서 나옴.
정정: 인프라 유지 + 활성화 작업이 정답. 콜백 시그니처가 DataPort 포트와 1:1 매칭되도록 미리 설계된 점, 컴포넌트 메타에 dataPorts 가 명시적으로 등록된 점, Template DB 에 connections 컬럼이 미리 만들어진 점이 "활성화 대기 중인 인프라" 임을 보여주는 신호였는데 초판에서 놓쳤음.
