Files
web-page/docs/superpowers/specs/2026-07-10-fe-module-refactor-instacards-design.md

99 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# InstaCards.jsx 분해 리팩토링 — 설계 (FE 모듈 sweep 서브프로젝트 4)
- **작성일**: 2026-07-10
- **역할/저장소**: FE (`web-ui`)
- **범위**: FE 대형 컴포넌트 분해 sweep의 **4번째 서브프로젝트**. 컨벤션은 sub-project 1~3에서 확립됨(web-ui/CLAUDE.md "기능 모듈 구조") — 이번은 적용만.
- **성격**: **behavior-preserving 순수 추출**. 로직/JSX/스타일 변경 없음.
---
## 1. 배경 & 목표
`src/pages/insta/InstaCards.jsx`(1013줄, `/insta` 뉴스 키워드 → AI 카드 자동 생성 페이지)를 기능 모듈 구조로 동작 보존 분해한다. 단일 파일에 **메인 shell + 11개 표현/패널 컴포넌트 + 1개 훅(`usePollTask`) + 헬퍼/상수**가 몰려 있다. 각 컴포넌트는 대부분 로컬 상태 + API를 직접 보유하는 자족적 패널이라 그대로 `components/`로 lift 한다.
비목표: 동작/스타일 변경, 신규 기능, 컴포넌트 내부 재분해(SlateDetail/KeywordsPanel 등), `handleCreateSlate`의 훅 추출.
---
## 2. 현 구조 맵 (InstaCards.jsx 1013줄)
- 헬퍼/상수: `fmtDate`(26), `CATEGORY_COLORS`(170), `CATEGORIES`(524), `KEYWORDS_PER_PAGE`(525), `PROMPT_NAMES`(941)
- 소형 표현: `StatusBadge`(3643), `TaskStatusBox`(8499)
- 훅: `usePollTask`(4582) — `{ taskId, task, start(taskId), clear() }` 반환, TriggerPanel이 사용
- 패널: `AccountFocusPanel`(101169), `ExternalTrendsPanel`(175267), `PreferenceImpactPanel`(268300), `TriggerPanel`(471521), `KeywordsPanel`(527637), `SlatesPanel`(638732), `PagesStrip`(733817), `SlateDetail`(818940), `PromptTemplatesEditor`(9431013)
- 메인 `InstaCards()` shell(301468, ~168줄): status 상태 + 탭(cards/trends, URL 동기화 `switchTab`) + `slateProgress` 배너 + `handleCreateSlate`(createInstaSlate → getInstaTask 인라인 폴링 최대 8분 → 완료 시 cards 탭 전환 + 슬레이트 선택) + 2탭 레이아웃 조합
**컴포넌트 중첩**: `SlatesPanel``SlateDetail``PagesStrip`. `TriggerPanel``usePollTask` + `TaskStatusBox`.
의존: `PullToRefresh`(src/components), `../../api` 헬퍼(getInstaStatus/createInstaSlate/getInstaTask/getInstaKeywords/getInstaSlates/getInstaSlate/renderInstaSlate/deleteInstaSlate/getInstaAssetUrl/instaPackageUrl/getInstaPrompt/putInstaPrompt/getInstaTrends/instaCollectTrends/getInstaPreferences/putInstaPreferences/instaCollectNews/instaExtractKeywords).
---
## 3. 목표 file map
```
src/pages/insta/
├── InstaCards.jsx # shell(~140줄): 탭바 + 상태헤더 + progress배너 + handleCreateSlate + 2탭 레이아웃 조합
├── InstaCards.css # 변경 없음 (shell에서 import 유지)
├── instaUtils.js # fmtDate(순수) + 다중 사용 상수(CATEGORY_COLORS)
├── instaUtils.test.js # fmtDate 유닛 (신규)
├── hooks/
│ └── usePollTask.js # 태스크 폴링 훅 { taskId, task, start, clear }
└── components/
├── StatusBadge.jsx # ({ status })
├── TaskStatusBox.jsx # ({ task })
├── TriggerPanel.jsx # () — usePollTask×2 + TaskStatusBox
├── KeywordsPanel.jsx # ({ onCreateSlate }) — CATEGORIES/KEYWORDS_PER_PAGE co-locate
├── SlatesPanel.jsx # ({ selectedId, onSelect }) — SlateDetail 렌더
├── SlateDetail.jsx # ({ slate, onDelete, onRender }) — PagesStrip 렌더
├── PagesStrip.jsx # ({ slateId, pageCount })
├── AccountFocusPanel.jsx # ()
├── ExternalTrendsPanel.jsx# ({ onCreateSlate })
├── PreferenceImpactPanel.jsx # ()
└── PromptTemplatesEditor.jsx # () — PROMPT_NAMES co-locate
```
**import 경로 규칙:**
- `components/*.jsx`·`hooks/*.js` → api `'../../../api'`; utils `'../instaUtils'`; 형제 컴포넌트/훅은 `'./TaskStatusBox'`·`'../hooks/usePollTask'` 등 상대 경로.
- `shell(InstaCards.jsx)` → 하위 `'./components/*'`, 훅 `'./hooks/usePollTask'`, utils `'./instaUtils'`, CSS `'./InstaCards.css'`, `PullToRefresh` `'../../components/PullToRefresh'`(기존 유지).
- 각 컴포넌트가 실제 사용하는 심볼만 import(빌드/lint가 누락·미사용 검출).
**상수 배치:** `fmtDate` + `CATEGORY_COLORS`(다중 컴포넌트 사용 가능성 → 공유) = instaUtils. `CATEGORIES`/`KEYWORDS_PER_PAGE`(KeywordsPanel 단독) → KeywordsPanel.jsx co-locate. `PROMPT_NAMES`(PromptTemplatesEditor 단독) → PromptTemplatesEditor.jsx co-locate. (구현 시 실제 사용처가 단일이면 co-locate, 2+면 instaUtils로 승격.)
---
## 4. shell 책임 (분해 후)
`InstaCards()`의 조합/오케스트레이션만 남긴다(동작 보존):
- 상태: `status`, `selectedSlateId`, `slateProgress`, `activeTab`.
- `switchTab`(URL 동기화), `loadStatus`(getInstaStatus), `useEffect` 로드.
- `handleCreateSlate`(createInstaSlate → getInstaTask 인라인 폴링 → cards 탭 전환 + 슬레이트 선택 + progress 배너) — **shell 유지**, `onCreateSlate` prop으로 KeywordsPanel·ExternalTrendsPanel에 전달.
- 렌더: 탭바 + progress 배너 + Cards 탭(PullToRefresh > 헤더 + TriggerPanel + KeywordsPanel + SlatesPanel + PromptTemplatesEditor) + Trends 탭(AccountFocusPanel + ExternalTrendsPanel + PreferenceImpactPanel).
---
## 5. 테스트 (안전망 — 현재 InstaCards 테스트 0)
1. **`instaUtils.test.js`(신규, 필수)** — `fmtDate`(현 구현: falsy → `''`; 유효 ISO → `new Date(iso).toLocaleDateString('ko-KR', {month:'short', day:'numeric', hour:'2-digit', minute:'2-digit'})`): falsy 3케이스(`''`/`null`/`undefined`) → `''` 고정; 유효 ISO → **비어있지 않은 문자열**(타임존/로케일 의존이라 정확 포맷 대신 non-empty smoke). 회귀 안전망.
2. 나머지 표현/패널 컴포넌트는 로컬 상태+API 위주 → 이번 범위 스모크 최소화(추출 검증은 build+lint+수동). StatusBadge 스모크(선택) 가능.
3. **검증 게이트:** 각 단계 `npm run build` + `npm run test:run` 전체 green(회귀 0) + `npm run lint` 신규/수정 파일 0 에러. 최종 개발서버 `/insta`: 2탭(Cards/Trends) 전환·URL 동기화, 트리거(뉴스 수집·키워드 추출), 키워드 목록·페이지네이션, 슬레이트 생성(progress 배너 → 완료 자동 전환)·목록·상세(SlateDetail)·페이지 스트립(PagesStrip)·삭제·렌더, 프롬프트 편집, Trends 3패널이 리팩토링 전과 동일 동작.
---
## 6. 완료 기준
- [ ] InstaCards.jsx가 shell(~140줄) + hooks/usePollTask + instaUtils(+test) + components/11로 분해, 동작/스타일 불변.
- [ ] `instaUtils.test.js` 통과, 전체 `npm run test:run` green.
- [ ] `npm run lint`·`npm run build` 통과.
- [ ] `/insta` 수동 검증 동일 동작(2탭·트리거·키워드·슬레이트 생성/상세/렌더/삭제·프롬프트·Trends).
---
## 7. 리스크 / 오픈 이슈
- **테스트 부재 컴포넌트 리팩토링**: 순수 추출 + build/lint/유닛/수동으로 위험 최소화.
- **컴포넌트 중첩 wiring**: SlatesPanel→SlateDetail→PagesStrip, TriggerPanel→usePollTask/TaskStatusBox 상대 import 경로 정확성(빌드가 검출).
- **상수 배치 판정**: 실제 사용처 수에 따라 co-locate vs instaUtils 승격(구현 시 grep 확인).
- **후속 follow-up**: SlateDetail(~122줄)·KeywordsPanel(~110줄) 내부 분할 여지 기록.
- **다음 서브프로젝트**: MusicStudio(1936, 최대·마지막).