ERP-node/docs/시스템_문제점_분석_및_개선_계획.md

28 KiB

WACE 시스템 문제점 분석 및 개선 계획

작성일: 2026-03-01 상태: 분석 완료, 계획 수립 목적: 반복적으로 발생하는 시스템 문제의 근본 원인 분석 및 구조적 개선 방안


목차

  1. 문제 요약
  2. 문제 1: AI(Cursor) 대화 길어질수록 정확도 저하
  3. 문제 2: 컴포넌트가 일관되지 않게 생성됨
  4. 문제 3: 코드 수정 시 다른 곳에 사이드 이펙트 발생
  5. 근본 원인 종합
  6. 개선 계획
  7. 우선순위 로드맵

1. 문제 요약

# 증상 빈도 심각도
1 Cursor로 오래 작업하면 정확도 떨어짐 매 세션
2 로우코드 컴포넌트 생성 시 오류, 비일관성 매 컴포넌트
3 수정/신규 코드가 다른 곳에 영향 (저장 안됨, 특정 기능 깨짐) 수시

세 문제는 독립적으로 보이지만, 하나의 구조적 원인에서 파생된다.


2. 문제 1: AI(Cursor) 대화 길어질수록 정확도 저하

2.1. 증상

  • 대화 초반에는 정확한 코드를 생성하다가, 30분~1시간 이상 작업하면 엉뚱한 코드 생성
  • 이전 맥락을 잊고 같은 질문을 반복하거나, 이미 수정한 부분을 되돌림
  • 관련 없는 파일을 수정하거나, 존재하지 않는 함수/변수를 참조

2.2. 원인 분석

AI의 컨텍스트 윈도우는 유한하다. 우리 코드베이스의 핵심 파일들이 비정상적으로 거대해서, AI가 한 번에 파악해야 할 정보량이 폭발한다.

거대 파일 목록 (상위 10개)

파일 줄 수 역할
frontend/lib/utils/buttonActions.ts 7,609줄 버튼 액션 전체 로직
frontend/components/screen/ScreenDesigner.tsx 7,559줄 화면 설계기
frontend/lib/registry/components/v2-table-list/TableListComponent.tsx 6,867줄 V2 테이블 컴포넌트
frontend/lib/registry/components/table-list/TableListComponent.tsx 6,829줄 레거시 테이블 컴포넌트
frontend/components/screen/EditModal.tsx 1,648줄 편집 모달
frontend/lib/registry/components/v2-button-primary/ButtonPrimaryComponent.tsx 1,524줄 버튼 컴포넌트
frontend/components/v2/V2Repeater.tsx 1,442줄 리피터 컴포넌트
frontend/components/screen/InteractiveScreenViewerDynamic.tsx 1,435줄 화면 뷰어
frontend/lib/utils/improvedButtonActionExecutor.ts 1,063줄 버튼 실행기
frontend/lib/registry/DynamicComponentRenderer.tsx 980줄 컴포넌트 렌더러

상위 3개 파일만 합쳐도 22,035줄이다. AI가 이 파일 하나를 읽는 것만으로도 컨텍스트의 상당 부분을 소모한다.

타입 안전성 부재

// frontend/types/component.ts:37-39
export interface ComponentConfig {
  [key: string]: any;  // 사실상 타입 검증 없음
}

// frontend/types/component.ts:56-78
export interface ComponentRendererProps {
  component: any;       // ComponentData인데 any로 선언
  // ... 중략 ...
  [key: string]: any;   // 여기도 any
}

any 타입이 핵심 인터페이스에 사용되어, AI가 "이 prop에 뭘 넣어야 하는지" 추론 불가. 사람이 봐도 모르는데 AI가 알 리가 없다.

이벤트 이름이 문자열 상수

// 이 이벤트들이 코드 전체에 흩어져 있음
window.dispatchEvent(new CustomEvent("refreshTable"));
window.dispatchEvent(new CustomEvent("closeEditModal"));
window.dispatchEvent(new CustomEvent("saveSuccessInModal"));
window.dispatchEvent(new CustomEvent("repeaterSaveComplete"));
window.dispatchEvent(new CustomEvent("refreshCardDisplay"));
window.dispatchEvent(new CustomEvent("refreshTableData"));
window.dispatchEvent(new CustomEvent("saveSuccess"));
window.dispatchEvent(new CustomEvent("closeScreenModal"));

문자열 기반이라 AI가 이벤트 흐름을 추적할 수 없다. 어떤 이벤트가 어디서 발생하고 어디서 수신되는지 정적 분석이 불가능하다.

2.3. 영향

  • AI가 파일 하나를 읽으면 다른 파일의 맥락을 잊음
  • 함수 시그니처를 추론하지 못하고 잘못된 파라미터를 넣음
  • 이벤트 기반 로직을 이해하지 못해 부정확한 코드 생성

3. 문제 2: 컴포넌트가 일관되지 않게 생성됨

3.1. 증상

  • 새 컴포넌트를 만들 때마다 구조가 다름
  • Config 패널의 UI 패턴이 컴포넌트마다 제각각
  • 같은 기능인데 어떤 컴포넌트는 동작하고 어떤 컴포넌트는 안 됨

3.2. 원인 분석

컴포넌트 수량과 중복

현재 등록된 컴포넌트 디렉토리: 81개

이 중 V2와 레거시가 병존하는 중복 쌍:

V2 버전 레거시 버전 기능
v2-table-list (6,867줄) table-list (6,829줄) 테이블
v2-button-primary (1,524줄) button-primary 버튼
v2-card-display card-display 카드 표시
v2-aggregation-widget aggregation-widget 집계 위젯
v2-file-upload file-upload 파일 업로드
v2-split-panel-layout split-panel-layout 분할 패널
v2-section-card section-card 섹션 카드
v2-section-paper section-paper 섹션 페이퍼
v2-category-manager category-manager 카테고리
v2-repeater repeater-field-group 리피터
v2-pivot-grid pivot-grid 피벗 그리드
v2-rack-structure rack-structure 랙 구조
v2-repeat-container repeat-container 반복 컨테이너

13쌍이 중복 존재. v2-table-listtable-list는 각각 6,800줄 이상으로, 거의 같은 코드가 두 벌 있다.

패턴은 있지만 강제되지 않음

컴포넌트 표준 구조:

v2-example/
├── index.ts              # createComponentDefinition()
├── ExampleRenderer.tsx   # AutoRegisteringComponentRenderer 상속
├── ExampleComponent.tsx  # 실제 UI
├── ExampleConfigPanel.tsx # 설정 패널 (선택)
└── types.ts              # ExampleConfig extends ComponentConfig

이 패턴을 문서(.cursor/rules/component-development-guide.mdc)에서 설명하고 있지만:

  1. 런타임 검증 없음: createComponentDefinition()이 ID 형식만 검증, 나머지는 자유
  2. Config 타입이 any: ComponentConfig = { [key: string]: any } → 아무 값이나 들어감
  3. 테스트 0개: 전체 프론트엔드에 테스트 파일 1개 (buttonDataflowPerformance.test.ts), 컴포넌트 테스트는 0개
  4. 스캐폴딩 도구 없음: 수동으로 파일을 만들고 index.ts에 import를 추가해야 함

컴포넌트 간 복잡도 격차

분류 예시 줄 수 외부 의존 Error Boundary
단순 표시형 v2-text-display ~100줄 거의 없음 없음
입력형 v2-input ~500줄 formData, eventBus 없음
버튼 v2-button-primary 1,524줄 buttonActions, apiClient, context, eventBus, modalDataStore 있음
테이블 v2-table-list 6,867줄 거의 모든 것 있음

100줄짜리와 6,867줄짜리가 같은 "컴포넌트"로 취급된다. AI에게 "컴포넌트 만들어"라고 하면 어떤 수준으로 만들어야 하는지 기준이 없다.

POP 컴포넌트는 완전 별도 시스템

frontend/lib/registry/
├── ComponentRegistry.ts      # 웹 컴포넌트 레지스트리
├── PopComponentRegistry.ts   # POP 컴포넌트 레지스트리 (별도 인터페이스)

같은 "컴포넌트"인데 등록 방식, 인터페이스, 설정 구조가 완전히 다르다.

3.3. 영향

  • 새 컴포넌트를 만들 때 "어떤 컴포넌트를 참고해야 하는지" 불명확
  • AI가 참조하는 컴포넌트에 따라 결과물이 달라짐
  • Config 구조가 제각각이라 설정 패널 UI도 불일치

4. 문제 3: 코드 수정 시 다른 곳에 사이드 이펙트 발생

4.1. 증상

  • 저장 로직 수정했더니 다른 화면에서 저장이 안 됨
  • 테이블 관련 코드 수정했더니 모달에서 특정 기능이 깨짐
  • 리피터 수정했더니 버튼 동작이 달라짐

4.2. 원인 분석

원인 A: window 전역 상태 오염

코드베이스 전체에서 window.__* 패턴 사용: 8개 파일, 32회 참조

전역 변수 정의 위치 사용 위치 위험도
window.__v2RepeaterInstances V2Repeater.tsx (220줄) EditModal.tsx, buttonActions.ts (4곳) 높음
window.__relatedButtonsTargetTables RelatedDataButtonsComponent.tsx (25줄) v2-table-list, table-list, buttonActions.ts 높음
window.__relatedButtonsSelectedData RelatedDataButtonsComponent.tsx (51줄) buttonActions.ts (3113줄) 높음
window.__unifiedRepeaterInstances UnifiedRepeater.tsx (110줄) UnifiedRepeater.tsx 중간
window.__AUTH_LOG authLogger.ts 디버깅용 낮음

사이드 이펙트 시나리오 예시:

1. V2Repeater 마운트 → window.__v2RepeaterInstances에 등록
2. EditModal이 저장 시 → window.__v2RepeaterInstances 체크
3. 만약 Repeater가 언마운트 타이밍에 늦게 정리되면?
   → EditModal은 "리피터가 있다"고 판단
   → 리피터 저장 로직 실행
   → 실제로는 리피터 데이터 없음
   → 저장 실패 또는 빈 데이터 저장

원인 B: 이벤트 스파게티

window.dispatchEvent(new CustomEvent(...)) 사용: 43개 파일, 총 120회 이상

주요 이벤트와 발신/수신 관계:

[refreshTable 이벤트]
발신 (8곳):
  - buttonActions.ts (5회)
  - BomItemEditorComponent.tsx
  - SelectedItemsDetailInputComponent.tsx
  - BomTreeComponent.tsx (2회)
  - ButtonPrimaryComponent.tsx (레거시)
  - ScreenModal.tsx (2회)
  - InteractiveScreenViewerDynamic.tsx

수신 (5곳):
  - v2-table-list/TableListComponent.tsx
  - table-list/TableListComponent.tsx
  - SplitPanelLayoutComponent.tsx
  - InteractiveScreenViewerDynamic.tsx
  - InteractiveScreenViewer.tsx
[closeEditModal 이벤트]
발신 (4곳):
  - buttonActions.ts (4회)

수신 (2곳):
  - EditModal.tsx
  - screens/[screenId]/page.tsx
[beforeFormSave 이벤트]
수신 (6곳):
  - V2Input.tsx
  - V2Repeater.tsx
  - BomItemEditorComponent.tsx
  - SelectedItemsDetailInputComponent.tsx
  - UniversalFormModalComponent.tsx
  - V2FormContext.tsx

문제: 이벤트 이름이 문자열 상수이고, 발신과 수신이 타입으로 연결되지 않음. refreshTable 이벤트를 refreshTableData로 오타내도 컴파일 에러 없이 런타임에서만 발견된다.

원인 C: 이중/삼중 이벤트 시스템

동시에 3개의 이벤트 시스템이 공존:

시스템 위치 방식 타입 안전
window.dispatchEvent 전역 CustomEvent 문자열 없음
v2EventBus lib/v2-core/events/EventBus.ts 타입 기반 pub/sub 있음
LegacyEventAdapter lib/v2-core/adapters/LegacyEventAdapter.ts 1번↔2번 브릿지 부분적

어떤 컴포넌트는 window.dispatchEvent를 쓰고, 어떤 컴포넌트는 v2EventBus를 쓰고, 또 어떤 컴포넌트는 둘 다 쓴다. 같은 이벤트가 두 시스템에서 동시에 발생할 수 있어 예측 불가능한 동작이 발생한다.

원인 D: SplitPanelContext 이름 충돌

같은 이름의 Context가 2개 존재:

위치 용도 제공하는 것
frontend/contexts/SplitPanelContext.tsx 데이터 전달 selectedLeftData, transfer(), registerReceiver()
frontend/lib/registry/components/split-panel-layout/SplitPanelContext.tsx 리사이즈/좌표 getAdjustedX(), dividerX, leftWidthPercent

import 경로에 따라 완전히 다른 Context를 가져온다. AI가 자동완성으로 잘못된 Context를 import하면 런타임에 undefined 에러가 발생한다.

원인 E: buttonActions.ts - 7,609줄의 신(God) 파일

이 파일 하나가 다음 기능을 전부 담당:

  • 저장 (INSERT/UPDATE/DELETE)
  • 모달 열기/닫기
  • 리피터 데이터 수집
  • 테이블 새로고침
  • 파일 업로드
  • 외부 API 호출
  • 화면 전환
  • 데이터 검증
  • 이벤트 발송 (33회)
  • window 전역 상태 읽기 (5회)

이 파일의 한 줄을 수정하면, 위의 모든 기능이 영향을 받을 수 있다.

원인 F: 레거시-V2 코드 동시 존재

v2-table-list/TableListComponent.tsx   (6,867줄)
table-list/TableListComponent.tsx      (6,829줄)

거의 같은 코드가 두 벌. 한쪽을 수정하면 다른 쪽은 수정 안 되어 동작이 달라진다. 또한 두 컴포넌트가 같은 전역 이벤트를 수신하므로, 한 화면에 둘 다 있으면 이중으로 반응할 수 있다.

원인 G: Error Boundary 미적용

컴포넌트 Error Boundary
v2-button-primary 있음
v2-table-list 있음
v2-repeater 있음
v2-input 없음
v2-select 없음
v2-card-display 없음
v2-text-display 없음
기타 대부분 없음

Error Boundary가 없는 컴포넌트에서 에러가 발생하면, 상위 컴포넌트까지 전파되어 화면 전체가 깨진다.

4.3. 사이드 이펙트 발생 위험 지도

┌─────────────────────────────────────────────────────┐
│                  buttonActions.ts                     │
│                    (7,609줄)                          │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐           │
│  │ 저장 로직 │  │ 모달 로직 │  │ 이벤트   │           │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘           │
└───────┼──────────────┼─────────────┼─────────────────┘
        │              │             │
        ▼              ▼             ▼
┌──────────────┐ ┌──────────┐ ┌─────────────────┐
│ window.__v2  │ │EditModal │ │ CustomEvent     │
│ RepeaterInst │ │(1,648줄) │ │ "refreshTable"  │
│ ances        │ │          │ │ "closeEditModal" │
└──────┬───────┘ └────┬─────┘ │ "saveSuccess"   │
       │              │       └───────┬─────────┘
       ▼              │               │
┌──────────────┐      │        ┌──────▼───────┐
│ V2Repeater   │◄─────┘        │ TableList    │
│ (1,442줄)    │               │ (6,867줄)    │
└──────────────┘               │ + 레거시      │
                               │ (6,829줄)    │
                               └──────────────┘

위 그래프에서 어디를 수정하든 화살표를 따라 다른 곳에 영향이 전파된다.


5. 근본 원인 종합

세 가지 문제의 근본 원인은 하나다: 경계(Boundary)가 없는 아키텍처

근본 원인 문제 1 영향 문제 2 영향 문제 3 영향
거대 파일 (God File) AI 컨텍스트 소모 참조할 기준 불명확 수정 영향 범위 광범위
any 타입 남발 AI 타입 추론 불가 Config 검증 없음 런타임 에러
문자열 이벤트 AI 이벤트 흐름 추적 불가 이벤트 패턴 불일치 이벤트 누락/오타
window 전역 상태 AI 상태 추적 불가 컴포넌트 간 의존 증가 상태 오염
테스트 부재 (0개) 변경 검증 불가 컴포넌트 계약 불명 사이드 이펙트 감지 불가
레거시-V2 중복 (13쌍) AI 혼동 어느 쪽을 기준으로? 한쪽만 수정 시 불일치

6. 개선 계획

Phase 1: 즉시 효과 (1~2주) - 안전장치 설치

1-1. 이벤트 이름 상수화

현재:

window.dispatchEvent(new CustomEvent("refreshTable"));

개선:

// frontend/lib/constants/events.ts
export const EVENTS = {
  REFRESH_TABLE: "refreshTable",
  CLOSE_EDIT_MODAL: "closeEditModal",
  SAVE_SUCCESS: "saveSuccess",
  SAVE_SUCCESS_IN_MODAL: "saveSuccessInModal",
  REPEATER_SAVE_COMPLETE: "repeaterSaveComplete",
  REFRESH_CARD_DISPLAY: "refreshCardDisplay",
  REFRESH_TABLE_DATA: "refreshTableData",
  CLOSE_SCREEN_MODAL: "closeScreenModal",
  BEFORE_FORM_SAVE: "beforeFormSave",
} as const;

// 사용
window.dispatchEvent(new CustomEvent(EVENTS.REFRESH_TABLE));

효과: 오타 방지, AI가 이벤트 흐름 추적 가능, IDE 자동완성 지원 위험도: 낮음 (기능 변경 없음, 리팩토링만) 소요 예상: 2~3시간

1-2. window 전역 변수 타입 선언

현재: window.__v2RepeaterInstances를 사용하지만 타입 선언 없음

개선:

// frontend/types/global.d.ts
declare global {
  interface Window {
    __v2RepeaterInstances?: Set<string>;
    __unifiedRepeaterInstances?: Set<string>;
    __relatedButtonsTargetTables?: Set<string>;
    __relatedButtonsSelectedData?: {
      tableName: string;
      selectedRows: any[];
    };
    __AUTH_LOG?: { show: () => void };
    __COMPONENT_REGISTRY__?: Map<string, any>;
  }
}

효과: 타입 안전성 확보, AI가 전역 상태 구조 이해 가능 위험도: 낮음 (타입 선언만, 런타임 변경 없음) 소요 예상: 1시간

1-3. ComponentConfig에 제네릭 타입 적용

현재:

export interface ComponentConfig {
  [key: string]: any;
}

개선:

export interface ComponentConfig {
  [key: string]: unknown;  // any → unknown으로 변경하여 타입 체크 강제
}

// 각 컴포넌트에서
export interface ButtonPrimaryConfig extends ComponentConfig {
  text: string;           // 구체적 타입
  action: ButtonAction;   // 구체적 타입
  variant?: "default" | "destructive" | "outline";
}

효과: 잘못된 config 값 사전 차단 위험도: 중간 (기존 any 사용처에서 타입 에러 발생 가능, 점진적 적용 필요) 소요 예상: 3~5일 (점진적)


Phase 2: 구조 개선 (2~4주) - 핵심 분리

2-1. buttonActions.ts 분할

현재: 7,609줄, 1개 파일

개선 목표: 도메인별 분리

frontend/lib/actions/
├── index.ts                  # re-export
├── types.ts                  # 공통 타입
├── saveActions.ts            # INSERT/UPDATE 저장 로직
├── deleteActions.ts          # DELETE 로직
├── modalActions.ts           # 모달 열기/닫기
├── tableActions.ts           # 테이블 새로고침, 데이터 조작
├── repeaterActions.ts        # 리피터 데이터 수집/저장
├── fileActions.ts            # 파일 업로드/다운로드
├── navigationActions.ts      # 화면 전환
├── validationActions.ts      # 데이터 검증
└── externalActions.ts        # 외부 API 호출

효과:

  • 저장 로직 수정 시 saveActions.ts만 영향
  • AI가 관련 파일만 읽으면 됨 (7,600줄 → 평균 500줄)
  • import 관계로 의존성 명확화

위험도: 높음 (가장 많이 사용되는 파일, 신중한 분리 필요) 소요 예상: 1~2주

2-2. 이벤트 시스템 통일

현재: 3개 시스템 공존 (window CustomEvent, v2EventBus, LegacyEventAdapter)

개선:

// v2EventBus로 통일, 타입 안전한 이벤트 정의
interface EventMap {
  "table:refresh": { tableId?: string };
  "modal:close": { modalId: string };
  "form:save": { formData: Record<string, any> };
  "form:saveComplete": { success: boolean; message?: string };
  "repeater:saveComplete": { repeaterId: string };
}

// 사용
v2EventBus.emit("table:refresh", { tableId: "order_table" });
v2EventBus.on("table:refresh", (data) => { /* data.tableId 타입 안전 */ });

마이그레이션 전략:

  1. v2EventBusEventMap 타입 추가
  2. 새 코드는 반드시 v2EventBus 사용
  3. 기존 window.dispatchEventv2EventBus로 점진적 교체
  4. LegacyEventAdapter에서 양방향 브릿지 유지 (과도기)
  5. 모든 교체 완료 후 LegacyEventAdapter 제거

효과: 이벤트 흐름 추적 가능, 타입 안전, 디버깅 용이 위험도: 중간 (과도기 브릿지로 안전하게 전환) 소요 예상: 2~3주

2-3. window 전역 상태 → Zustand 스토어 전환

현재:

window.__v2RepeaterInstances = new Set();
window.__relatedButtonsSelectedData = { tableName, selectedRows };

개선:

// frontend/lib/stores/componentInstanceStore.ts
import { create } from "zustand";

interface ComponentInstanceState {
  repeaterInstances: Set<string>;
  relatedButtonsTargetTables: Set<string>;
  relatedButtonsSelectedData: {
    tableName: string;
    selectedRows: any[];
  } | null;

  registerRepeater: (key: string) => void;
  unregisterRepeater: (key: string) => void;
  setRelatedData: (data: { tableName: string; selectedRows: any[] }) => void;
  clearRelatedData: () => void;
}

export const useComponentInstanceStore = create<ComponentInstanceState>((set) => ({
  repeaterInstances: new Set(),
  relatedButtonsTargetTables: new Set(),
  relatedButtonsSelectedData: null,

  registerRepeater: (key) =>
    set((state) => {
      const next = new Set(state.repeaterInstances);
      next.add(key);
      return { repeaterInstances: next };
    }),
  unregisterRepeater: (key) =>
    set((state) => {
      const next = new Set(state.repeaterInstances);
      next.delete(key);
      return { repeaterInstances: next };
    }),
  setRelatedData: (data) => set({ relatedButtonsSelectedData: data }),
  clearRelatedData: () => set({ relatedButtonsSelectedData: null }),
}));

효과:

  • 상태 변경 추적 가능 (Zustand devtools)
  • 컴포넌트 리렌더링 최적화 (selector 사용)
  • window 오염 제거

위험도: 중간 소요 예상: 1주


Phase 3: 품질 강화 (4~8주) - 예방 체계

3-1. 레거시 컴포넌트 제거

목표: V2-레거시 중복 13쌍 → V2만 유지

전략:

  1. 각 중복 쌍에서 레거시 사용처 검색
  2. 사용처가 없는 레거시 컴포넌트 즉시 제거
  3. 사용처가 있는 경우 V2로 교체 후 제거
  4. components/index.ts에서 import 제거

효과: 코드베이스 ~15,000줄 감소, AI 혼동 제거 소요 예상: 2~3주

3-2. 컴포넌트 스캐폴딩 CLI

목표: npx create-v2-component my-component 실행 시 표준 구조 자동 생성

$ npx create-v2-component my-widget --category data

생성 완료:
  frontend/lib/registry/components/v2-my-widget/
  ├── index.ts              # 자동 생성
  ├── MyWidgetRenderer.tsx   # 자동 생성
  ├── MyWidgetComponent.tsx  # 템플릿
  ├── MyWidgetConfigPanel.tsx # 템플릿
  └── types.ts              # Config 인터페이스 템플릿

  components/index.ts에 import 자동 추가 완료

효과: 컴포넌트 구조 100% 일관성 보장 소요 예상: 3~5일

3-3. 핵심 컴포넌트 통합 테스트

목표: 사이드 이펙트 감지용 테스트 작성

// __tests__/integration/save-flow.test.ts
describe("저장 플로우", () => {
  it("버튼 저장 → refreshTable 이벤트 발생", async () => {
    const listener = vi.fn();
    v2EventBus.on("table:refresh", listener);
    
    await executeSaveAction({ tableName: "test_table", data: mockData });
    
    expect(listener).toHaveBeenCalledTimes(1);
  });

  it("리피터가 있을 때 저장 → 리피터 데이터도 포함", async () => {
    useComponentInstanceStore.getState().registerRepeater("detail_table");
    
    const result = await executeSaveAction({ tableName: "master_table", data: mockData });
    
    expect(result.repeaterDataCollected).toBe(true);
  });
});

대상: 저장/삭제/모달/리피터 흐름 (가장 빈번하게 깨지는 부분) 효과: 코드 수정 후 즉시 사이드 이펙트 감지 소요 예상: 2~3주

3-4. SplitPanelContext 통합

목표: 이름이 같은 2개의 Context → 1개로 통합 또는 명확히 분리

방안 A - 통합:

// frontend/contexts/SplitPanelContext.tsx에 통합
interface SplitPanelContextValue {
  // 데이터 전달 (기존 contexts/ 버전)
  selectedLeftData: any;
  transfer: (data: any) => void;
  registerReceiver: (handler: (data: any) => void) => void;
  // 리사이즈 (기존 components/ 버전)
  getAdjustedX: (x: number) => number;
  dividerX: number;
  leftWidthPercent: number;
}

방안 B - 명확 분리:

// SplitPanelDataContext.tsx → 데이터 전달용
// SplitPanelResizeContext.tsx → 리사이즈용

효과: import 혼동 제거 소요 예상: 2~3일


Phase 4: 장기 개선 (8주+) - 아키텍처 전환

4-1. 거대 컴포넌트 분할

대상 파일 현재 줄 수 분할 목표
v2-table-list/TableListComponent.tsx 6,867줄 훅 분리, 렌더링 분리 → 각 1,000줄 이하
ScreenDesigner.tsx 7,559줄 패널별 분리 → 각 1,500줄 이하
EditModal.tsx 1,648줄 저장/폼/UI 분리 → 각 500줄 이하
ButtonPrimaryComponent.tsx 1,524줄 액션 실행 분리 → 각 500줄 이하

4-2. Config 스키마 검증 (Zod)

// v2-button-primary/types.ts
import { z } from "zod";

export const ButtonPrimaryConfigSchema = z.object({
  text: z.string().default("버튼"),
  variant: z.enum(["default", "destructive", "outline", "secondary", "ghost"]).default("default"),
  action: z.object({
    type: z.enum(["save", "delete", "navigate", "custom"]),
    targetTable: z.string().optional(),
    // ...
  }),
});

export type ButtonPrimaryConfig = z.infer<typeof ButtonPrimaryConfigSchema>;

createComponentDefinition()에서 스키마 검증을 강제하여 잘못된 config가 등록 시점에 차단되도록 한다.


7. 우선순위 로드맵

즉시 (이번 주)

  • 1-1: 이벤트 이름 상수 파일 생성 (frontend/lib/constants/events.ts)
  • 1-2: window 전역 변수 타입 선언 (frontend/types/global.d.ts)

단기 (1~2주)

  • 2-3: window 전역 상태 → Zustand 스토어 전환
  • 1-3: ComponentConfig anyunknown 점진적 적용

중기 (2~4주)

  • 2-1: buttonActions.ts 분할 (7,609줄 → 도메인별)
  • 2-2: 이벤트 시스템 통일 (v2EventBus 기반)
  • 3-4: SplitPanelContext 통합/분리

장기 (4~8주)

  • 3-1: 레거시 컴포넌트 13쌍 제거
  • 3-2: 컴포넌트 스캐폴딩 CLI
  • 3-3: 핵심 플로우 통합 테스트
  • 4-1: 거대 컴포넌트 분할
  • 4-2: Config 스키마 Zod 검증

부록: 수치 요약

지표 현재 목표
최대 파일 크기 7,609줄 1,500줄 이하
컴포넌트 수 81개 (13쌍 중복) ~55개 (중복 제거)
window 전역 변수 5개 0개
이벤트 시스템 3개 공존 1개 (v2EventBus)
테스트 파일 1개 핵심 플로우 최소 10개
any 타입 사용 (핵심 인터페이스) 3곳 0곳
SplitPanelContext 중복 2개 1개 (또는 명확 분리)