# Phase 5 Frontend — 실행 계획

**상위 문서**: [02-final-roadmap.md §Phase 5](./02-final-roadmap.md#phase-5--frontend-서버-상태-재구성-2-4주-)
**작성일**: 2026-04-20
**상태**: **전체 완료 (2026-04-20 v0.6.0 태그)**. EpisodeDetail 1,358→148 (-89%). 전 세션(A/B/B2/C/C2/D/E) 완료.

## 진행 로그

| 세션 | 상태 | 커밋 | 실측 |
|------|------|------|------|
| A. 기반 | ✅ 완료 | `8da8473` v0.5.25 | LOC 1,358→1,292 (-66), useState 34→28 (-6) |
| B. EpisodeDetail 1차 | ✅ 완료 | `dd5e9f2` v0.5.26 | LOC 1,292→1,263 (-29), useState 28→25 (-3), mutation 패턴 확립 |
| B2. 잔여 훅 | ✅ 완료 | `d893190` v0.5.27 | LOC 1,263→1,208 (-55), useState 25→20 (-5), polling 통합 |
| C. 분할 | ✅ 완료 | `89857d2` v0.5.28 | LOC 1,208→790 (-418), useState 20→12 (-8), Header/WorldGuide/ActionBar 3개, B2 H2 완전 해소 |
| D. 다른 페이지 | ✅ 완료 | `5eab95e` v0.5.29 | Dashboard/Episodes/ProjectDetail 전환, 페이지 LOC 2,324→2,085 (-239), Entities invalidate(B M1) |
| D 후속 | ✅ 완료 | `66d4e4e` v0.5.30 | Claude 리뷰 High 6건 수용 (canManage my_role, role rollback onSettled, 조건부 polling, planningMsg→toast, useActivities overview, AbortController) |
| C2. Stills 분할 | ✅ 완료 | `4a88507` v0.5.31 | EpisodeDetail 790→148 (-642), useStillImages/useStillMutations(15개)/EpisodeStills(524) + 일반인용 HTML + architecture docs |
| E. 품질/문서 | ✅ 완료 | `631a082` v0.6.0 | backend except:pass 38→0 (LOG 25 / INTENTIONAL 13), README 갱신, Phase 5 완결 태그 |

---

## 최종 실측 (2026-04-20 v0.6.0)

| 페이지 | LOC 전 → 후 | useState 전 → 후 | 변화 |
|--------|------------|-----------------|------|
| EpisodeDetail.tsx | 1,358 → **148** | 34 → **1** | −89% / −97% |
| ProjectDetail.tsx | 483 → 363 | 16 → 2 | −25% / −88% |
| Episodes.tsx | 360 → 268 | 10 → 5 | −26% / −50% |
| Dashboard.tsx | 227 → 196 | 8 → 3 | −14% / −63% |
| **합계** | **2,428 → 975** | **68 → 11** | **−60% / −84%** |

> EpisodeStills.tsx (524 LOC, useState 2) 신규로 분할되어 page 바디 외부에서 관리. shell 구조 완성.

Backend `except...pass`: 38건 → **0건** (세션 E).

**로드맵 기준 대비 실측**:
- 총 작업량 2~4주 예상 → 실측 **1일 집중 투입** (2026-04-20). 기존 리팩토링 기반(Phase 0~4)이 단단해 빠르게 진행.
- ProjectDetail/Episodes useState가 예상보다 많았으나 React Query 기반 전환으로 더 큰 감소 달성.

---

## 세션 분할

| 세션 | 범위 | 산출물 | 예상 | 리스크 |
|------|------|--------|------|--------|
| **A. 기반** | React Query 설치 + Provider + useQuery 5개 훅 | `hooks/api/useEpisode`, `useStills`, `useEntities`, `useWorldGuide`, `useProgress` | 1세션 | 낮음 |
| **B. EpisodeDetail 1차** | 나머지 훅 5+ + data fetching 전환 (useState 34→20) | mutation 훅(토글/force) + optimistic update | 2세션 | 중 |
| **C. EpisodeDetail 분할** | 1,358 → ~500줄. 5개 sub-component | `components/episode/` (Header/Stills/Entities/WorldGuide/Pipeline) | 2세션 | 중 (회귀) |
| **D. 다른 페이지** | ProjectDetail/Episodes/Dashboard 전환 | 전 페이지 React Query | 1~2세션 | 낮음 |
| **E. 품질/문서** | except...pass 38건 분류 + docs 갱신 | 코드 + docs/architecture/* | 1세션 | 낮음 |

**총 7~8세션, 1~2주 집중 투입**.

---

## 주요 설계 결정 (세션 A~B 초기 확정)

### 1. SSE progress 통합 방식
- 옵션 (a): 독립 `useEffect` + `queryClient.setQueryData`로 수동 주입 ← **권장**
- 옵션 (b): React Query `useQuery` + `enabled: isStreaming` flag
- 옵션 (c): 외부 라이브러리(`react-use-sse`) 도입

**결정 근거**: (a)가 가장 명시적. SSE 수명주기를 React Query 내부에 숨기지 않음. 기존 `useSSE` 같은 훅이 이미 있으면 재사용.

### 2. Optimistic update 패턴
- 토글(is_selected), force run 등 mutation에서 rollback 전략
- React Query `onMutate` + `onError` rollback 표준 패턴 사용.

### 3. 캐시 키 계층
- `["episode", episodeId]` — 에피소드 메타
- `["episode", episodeId, "stills"]` — 스틸 목록
- `["episode", episodeId, "entities"]` — 엔티티
- `["episode", episodeId, "steps"]` — 단계 상태
- 무효화 범위: 토글 변경 시 `["episode", eid, "stills"]`만 invalidate.

### 4. 기존 `api/client.ts`와 통합
- 기존 `api<T>(url, opts)` 함수를 useQuery `queryFn` 안에서 래핑.
- client 교체 최소화 — 기존 auth/timeout 처리 유지.

---

## 세션 A 상세 (최초 실행)

### 작업
1. `cd frontend && npm install @tanstack/react-query @tanstack/react-query-devtools`
2. `frontend/src/main.tsx`: `QueryClientProvider` 추가
   ```tsx
   const queryClient = new QueryClient({
     defaultOptions: {
       queries: { staleTime: 5 * 60 * 1000, retry: 1 },
     },
   })
   ```
3. 신규 디렉토리: `frontend/src/hooks/api/`
4. 훅 2개 PoC (`useEpisode`, `useStills`):
   ```tsx
   export function useEpisode(projectId: string, episodeId: string) {
     return useQuery({
       queryKey: ["episode", episodeId],
       queryFn: () => api<Episode>(`/api/v1/projects/${projectId}/episodes/${episodeId}`),
       enabled: !!episodeId,
     })
   }
   ```
5. `EpisodeDetail.tsx`에서 위 2개 데이터 fetching만 훅으로 전환.

### 완료 조건
- [ ] 개발 서버 정상 기동 (`npm run dev`)
- [ ] EpisodeDetail 페이지 기존 기능 그대로 (회귀 없음)
- [ ] React Query devtools에서 2개 쿼리 활성 상태 확인
- [ ] TypeScript 타입 체크 통과
- [ ] 커밋 + 듀얼 리뷰

### 예상 결과
- useState 34 → 32 (2개 감소)
- LOC 소폭 감소 (~1,340)
- React Query 기반 확보로 후속 세션 가속

---

## 리스크

| 리스크 | 완화 |
|--------|------|
| UI 회귀 | 각 세션 끝 수동 E2E (금월도 + SRD 2개 프로젝트) |
| Optimistic rollback 복잡성 | 세션 B 초반에 패턴 1개 확립 후 나머지 확장 |
| SSE 통합 설계 미숙 | 세션 A에서 `useProgress` 훅 PoC로 검증 |
| 기존 훅 충돌 (useAuth, useI18n) | 세션 A에서 충돌 여부 조기 확인 |

---

## 완료 검증 체크리스트 (Phase 5 전체)

- [ ] `EpisodeDetail.tsx` 1,358 → ~500줄, useState ≤ 15
- [ ] 모든 API 요청이 `useQuery`/`useMutation` 경유
- [ ] React Query devtools에서 cache 확인
- [ ] Optimistic update + rollback 동작
- [ ] SSE progress 정상 수신
- [ ] `except: pass` 0건 (backend)
- [ ] `docs/architecture/` 전체 최신화 → v0.6.0 tag
- [ ] E2E playwright: core flows (수동 → 자동 이월)

---

## 다음 세션 판단 기준

세션 A 커밋 후:
- **순조** → 세션 B 착수
- **저항** → 설계 재평가 (예: React Query 대신 SWR, 또는 Zustand로 state 통합)
