# 프런트엔드 리뷰

## 최신 증거 요약

2026-04-21 기준으로 프런트는 "상위 페이지 구조는 정리됐지만, 핵심 복잡도와 품질 부채가 하위 컴포넌트에 집중된 상태"라고 보는 편이 정확하다.

- 분석 시작 계약이 화면마다 갈린다.
  - `frontend/src/pages/EpisodeDetail.tsx:81-85`는 `/steps/run-all?category=analysis`를 호출한다.
  - `frontend/src/pages/Episodes.tsx:46-59`는 `useAnalyzeEpisode`를 경유한다.
  - `frontend/src/hooks/api/mutations/useAnalyzeEpisode.ts:11-13`은 여전히 `/episodes/{id}/analyze`를 호출한다.
- strict ESLint flat config는 실제로 켜져 있다.
  - `frontend/eslint.config.js:8-17`은 `@eslint/js`, `typescript-eslint`, `react-hooks`, `react-refresh`의 recommended preset을 모두 확장한다.
  - 그런데 `cd frontend && npm run lint`는 현재 `77 errors, 3 warnings`로 실패한다.
- 프런트 테스트 lane은 없다.
  - `frontend/package.json:6-10`에 `test` 스크립트가 없다.
  - `rg --files frontend | rg '(test|spec)\\.(ts|tsx|js|jsx)$'` 결과도 비어 있다.
- 배포 빌드는 통과하지만 번들 경고가 크다.
  - `cd frontend && npm run build`는 성공한다.
  - 다만 `dist/assets/index-CrIr_h9I.js`가 `987.79 kB`로 찍히며 Vite의 500 kB 경고를 넘긴다.

## 현재 강점

### 1. React Query 기반 화면 조립은 실제다

- `frontend/package.json:12-19`에 `@tanstack/react-query`가 실제 의존성으로 들어 있다.
- `frontend/src/pages/EpisodeDetail.tsx`는 `useEpisode`, `useWorldRules`, `useProgress`, `useGenStatus`를 조합한다.
- `EpisodeDetail` 페이지 자체는 149줄 수준으로 얇아져 있다.

해석:

- 상위 페이지를 얇게 만들고 query 훅으로 읽기 책임을 모으는 방향 자체는 맞다.
- 문제는 복잡도가 사라진 것이 아니라 `EpisodeStills`, `SceneVariationCard`, `PipelineStepsPanel` 같은 하위 컴포넌트로 이동했다는 점이다.

## 주요 문제

### 1. 분석 시작 경로가 아직 하나로 정리되지 않았다

근거:

- `frontend/src/pages/EpisodeDetail.tsx:81-85`는 StepRunner 경로 `/steps/run-all?category=analysis`를 사용한다.
- `frontend/src/pages/Episodes.tsx:46-59`는 `useAnalyzeEpisode` mutation을 호출한다.
- `frontend/src/hooks/api/mutations/useAnalyzeEpisode.ts:11-13`은 legacy `/episodes/{id}/analyze`를 사용한다.

영향:

- 사용자 입장에서는 같은 "분석 시작" 버튼인데, 실제 런타임 계약은 화면마다 다르다.
- 백엔드가 `/analyze`를 compatibility shim으로 유지하더라도, 프런트는 이미 StepRunner를 공식 경로로 쓰기 시작했기 때문에 drift가 계속 쌓인다.
- 문서/운영 로그/트러블슈팅에서 "어느 경로가 정답인가"가 다시 흔들린다.

판단:

- 이 이슈는 프런트 구조 문제이면서 동시에 제품 계약 문제다.
- `Episodes` 화면이 StepRunner로 수렴하지 않으면, 이후 테스트/문서/운영 정리도 일관되게 끝나지 않는다.

### 2. 복잡도 hotspot은 여전히 크다

실측 LOC:

| 파일 | LOC | 의미 |
|---|---:|---|
| `frontend/src/components/shared/SceneVariationCard.tsx` | 1606 | gallery/prompt/dependency editor가 한 파일에 공존 |
| `frontend/src/pages/Entities.tsx` | 1258 | 데이터 조회, 일괄 실행, 편집, 재시도 UI가 한 페이지에 집중 |
| `frontend/src/components/episode/EpisodeStills.tsx` | 515 | mutation wrapper와 still/entity 조작 로직이 한 파일에 집중 |
| `frontend/src/components/shared/PipelineStepsPanel.tsx` | 463 | step 조회, 실행, run-all, config 저장이 공존 |
| `frontend/src/components/shared/ImageGalleryModal.tsx` | 436 | 상태 동기화, marker parsing, angle/color UI가 한 모달에 공존 |

해석:

- `EpisodeDetail`는 얇아졌지만, 제품 난이도는 사실상 위 다섯 파일에 몰려 있다.
- 특히 `SceneVariationCard`는 재사용 컴포넌트라기보다 독립된 소형 화면에 가깝다.

### 3. strict lint 기준은 있는데, 코드베이스가 그 기준을 아직 만족하지 못한다

실행 결과:

- `frontend/eslint.config.js:8-17`에서 recommended preset을 넓게 적용한다.
- `cd frontend && npm run lint` 결과는 `77 errors, 3 warnings`다.

대표 위반:

- `@typescript-eslint/no-explicit-any`
- `@typescript-eslint/no-unused-vars`
- `react-hooks/exhaustive-deps`
- `react-hooks/set-state-in-effect`
- `no-useless-escape`

품질 부채가 특히 집중된 파일:

| 파일 | `any` | `catch(` | 메모 |
|---|---:|---:|---|
| `EpisodeStills.tsx` | 21 | 17 | 가장 큰 overlap. lint warning도 같이 보유 |
| `Entities.tsx` | 5 | 15 | 페이지 단위 예외 처리와 느슨한 데이터 접근이 많음 |
| `ProjectDetail.tsx` | 7 | 6 | page-level orchestration이 타입 없이 누적 |
| `EpisodeWorldGuide.tsx` | 8 | 4 | any suppressions가 누적 |
| `PipelineStepsPanel.tsx` | 3 | 4 | run panel + config UI 결합 |
| `PromptManager.tsx` | 1 | 6 | broad catch 중심 |

판단:

- 문제는 "전체 파일이 조금씩 나쁘다"가 아니다.
- 실제 품질 이슈는 `any`와 broad `catch`가 겹치는 몇몇 화면/패널에 집중돼 있다.
- 따라서 lint 0을 만들려면 전역 sweep보다 hotspot 파일 정리가 더 효율적이다.

### 4. `useEventSource`는 아직 제품 흐름에 들어오지 못했다

근거:

- `frontend/src/hooks/useEventSource.ts:3-22`가 존재한다.
- `rg -n "useEventSource\\(" frontend/src -g '*.ts' -g '*.tsx'` 결과는 비어 있다.
- 같은 파일 `7-20`은 `if (!url) { setData(null); return }` 때문에 `react-hooks/set-state-in-effect`를 위반한다.
- `frontend/src/components/shared/ImageGalleryModal.tsx:88-94`도 같은 lint rule을 위반한다.

영향:

- 현재 실시간 업데이트는 제품 계약이 아니라 미사용 실험 코드에 가깝다.
- 사용하지 않는 훅이 lint debt까지 만들고 있으므로, "나중에 쓸 수도 있음" 상태로 남겨두는 비용이 이미 발생하고 있다.

판단:

- `useEventSource`는 통합하거나 삭제하는 둘 중 하나로 정리해야 한다.
- `ImageGalleryModal`의 동기 setState effect도 같은 시점에 정리하는 편이 낫다.

### 5. 빌드는 통과하지만 번들 건강도는 좋지 않다

실행 결과:

- `cd frontend && npm run build`는 성공했다.
- 출력 chunk는 `dist/assets/index-CrIr_h9I.js 987.79 kB`였고, Vite가 500 kB 초과 경고를 냈다.

해석:

- "배포 불가" 상태는 아니지만, 현재 화면 구성은 초기 JS 비용이 크다.
- 무거운 UI가 `SceneVariationCard`, `ImageGalleryModal`, still 편집 흐름에 몰려 있기 때문에, 지금 구조 그대로 기능을 더 얹으면 번들 경고는 더 커질 가능성이 높다.

### 6. 프런트 자동화 테스트는 아직 시작선에도 올라오지 못했다

근거:

- `frontend/package.json:6-10`에는 `dev`, `build`, `lint`, `preview`만 있다.
- `frontend/package.json:21-33`에도 Vitest/Jest/Playwright/Cypress 계열 의존성이 없다.
- `rg --files frontend | rg '(test|spec)\\.(ts|tsx|js|jsx)$'` 결과가 없다.

영향:

- React Query invalidation, still mutation, variation editor, progress polling처럼 수동 회귀 확인이 비싼 화면이 보호받지 못한다.
- lint를 통과시켜도 동작 회귀를 잡아낼 자동화 수단이 없다.

## 우선순위 높은 프런트 작업

1. `Episodes`의 분석 시작을 `/steps/run-all?category=analysis`로 통일하고 `useAnalyzeEpisode`의 legacy 경로 사용을 종료
2. `EpisodeStills`, `Entities`, `ProjectDetail`, `PipelineStepsPanel`, `EpisodeWorldGuide` 순서로 `any`/broad `catch` 정리
3. `useEventSource`와 `ImageGalleryModal`의 `set-state-in-effect` 위반 정리
4. Vitest + RTL 기반 최소 smoke lane 도입
5. `SceneVariationCard`와 still/image editor 계열 UI를 분해하고 route/component 단위 code splitting 적용
