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

7.4 KiB
Raw Blame History

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탭 레이아웃 조합

컴포넌트 중첩: SlatesPanelSlateDetailPagesStrip. TriggerPanelusePollTask + 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는 단일 사용이라 ExternalTrendsPanel에 co-locate)
├── 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, 최대·마지막).