docs(refactor): InstaCards 분해 구현 계획 (4 task)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UHXzpsZQxKG9hQmNRfZjRS
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
# InstaCards.jsx 분해 리팩토링 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** `src/pages/insta/InstaCards.jsx`(1013줄)를 shell(~140줄) + `components/`11 + `hooks/usePollTask` + `instaUtils`(+test)로 동작 보존 분해한다.
|
||||
|
||||
**Architecture:** behavior-preserving 순수 추출. 각 컴포넌트/훅/헬퍼를 현 정의 그대로(verbatim) 새 파일로 옮기고 import로 연결. 로직/JSX/스타일/prop shape 불변. 컴포넌트를 이름으로 찾아(정의 시그니처 기준) 옮기며, 매 task 후 build+lint+전체 test green(회귀 0)을 유지한다.
|
||||
|
||||
**Tech Stack:** React 18 + Vite + Vitest + @testing-library/react. 설계: `docs/superpowers/specs/2026-07-10-fe-module-refactor-instacards-design.md`.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- **behavior-preserving**: 옮기는 코드는 verbatim. 로직/JSX/className/prop 참조/스타일 변경 금지. 리팩토링은 "정의를 파일로 이동 + import 연결"만.
|
||||
- **line 번호는 pre-refactor(1013줄) 기준 힌트**. 추출이 진행되면 InstaCards.jsx 줄이 바뀌므로 **컴포넌트/함수 이름·시그니처로 위치를 찾을 것**(예: `function KeywordsPanel({ onCreateSlate }) {`).
|
||||
- **import 경로**: `components/*.jsx`·`hooks/*.js` → api `'../../../api'`, utils `'../instaUtils'`, 형제 컴포넌트 `'./X'`, 훅 `'../hooks/usePollTask'`. shell(`InstaCards.jsx`) → 컴포넌트 `'./components/X'`, 훅 `'./hooks/usePollTask'`, utils `'./instaUtils'`, `PullToRefresh` `'../../components/PullToRefresh'`, CSS `'./InstaCards.css'`.
|
||||
- **import 최소·완전**: 각 파일은 실제 참조하는 심볼만 import. 매 task에서 shell의 이제 안 쓰는 api/컴포넌트 import 제거. `npm run lint`(no-unused-vars)와 `npm run build`가 누락·미사용을 검출 → 둘 다 0 에러로 몰 것.
|
||||
- **각 컴포넌트는 default export**.
|
||||
- **검증 게이트(매 task)**: `npm run test:run` 전체 green(회귀 0, 시작 165) + `npm run lint` 신규/수정 파일 0 에러 + `npm run build` `✓ built`.
|
||||
- 단일 사용 상수 co-locate: `CATEGORIES`/`KEYWORDS_PER_PAGE` → KeywordsPanel.jsx, `PROMPT_NAMES` → PromptTemplatesEditor.jsx, `CATEGORY_COLORS` → ExternalTrendsPanel.jsx. 다중 사용 `fmtDate` → instaUtils.js.
|
||||
|
||||
---
|
||||
|
||||
## 파일 구조 (분해 후)
|
||||
|
||||
```
|
||||
src/pages/insta/
|
||||
├── InstaCards.jsx # shell(~140줄): 탭바+상태헤더+progress배너+handleCreateSlate+2탭 조합
|
||||
├── InstaCards.css # 변경 없음
|
||||
├── instaUtils.js # fmtDate
|
||||
├── instaUtils.test.js # fmtDate 유닛
|
||||
├── hooks/usePollTask.js # { taskId, task, start, clear }
|
||||
└── components/
|
||||
├── StatusBadge.jsx TaskStatusBox.jsx TriggerPanel.jsx KeywordsPanel.jsx
|
||||
├── SlatesPanel.jsx SlateDetail.jsx PagesStrip.jsx
|
||||
└── AccountFocusPanel.jsx ExternalTrendsPanel.jsx PreferenceImpactPanel.jsx PromptTemplatesEditor.jsx
|
||||
```
|
||||
|
||||
**shell이 직접 렌더하는 7 컴포넌트**: TriggerPanel, KeywordsPanel, SlatesPanel, PromptTemplatesEditor, AccountFocusPanel, ExternalTrendsPanel, PreferenceImpactPanel. (StatusBadge/TaskStatusBox/SlateDetail/PagesStrip은 다른 컴포넌트가 렌더 → shell import 아님.)
|
||||
|
||||
**컴포넌트 참조 그래프**: TriggerPanel→usePollTask+TaskStatusBox / ExternalTrendsPanel→TaskStatusBox+CATEGORY_COLORS+fmtDate / SlatesPanel→SlateDetail+StatusBadge+fmtDate / SlateDetail→PagesStrip+StatusBadge / PromptTemplatesEditor→fmtDate+PROMPT_NAMES.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 기반 — instaUtils(+test) + usePollTask 훅 + 리프 컴포넌트(StatusBadge, TaskStatusBox)
|
||||
|
||||
**Files:**
|
||||
- Create: `src/pages/insta/instaUtils.js`, `src/pages/insta/instaUtils.test.js`
|
||||
- Create: `src/pages/insta/hooks/usePollTask.js`
|
||||
- Create: `src/pages/insta/components/StatusBadge.jsx`, `src/pages/insta/components/TaskStatusBox.jsx`
|
||||
- Modify: `src/pages/insta/InstaCards.jsx` (정의 4개 제거 + import)
|
||||
|
||||
**Interfaces:**
|
||||
- Produces: `fmtDate(iso) → string`; `usePollTask(onDone?) → { taskId, task, start(id), clear() }`; `StatusBadge({ status })`; `TaskStatusBox({ task })` (모두 default export, usePollTask는 named 아님 default).
|
||||
|
||||
- [ ] **Step 1: instaUtils.test.js 작성(RED)**
|
||||
|
||||
```js
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { fmtDate } from './instaUtils';
|
||||
|
||||
describe('instaUtils.fmtDate', () => {
|
||||
it('returns empty string for falsy input', () => {
|
||||
expect(fmtDate('')).toBe('');
|
||||
expect(fmtDate(null)).toBe('');
|
||||
expect(fmtDate(undefined)).toBe('');
|
||||
});
|
||||
it('returns a non-empty string for a valid ISO date (locale/tz-dependent format)', () => {
|
||||
const out = fmtDate('2026-07-10T09:30:00Z');
|
||||
expect(typeof out).toBe('string');
|
||||
expect(out.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: RED 확인** — Run: `npx vitest run src/pages/insta/instaUtils.test.js`. Expected: FAIL(`fmtDate`/모듈 미존재).
|
||||
|
||||
- [ ] **Step 3: instaUtils.js 작성(GREEN)** — 현 `InstaCards.jsx`의 `fmtDate`(26–34) verbatim, `export` 부여:
|
||||
|
||||
```js
|
||||
// src/pages/insta/instaUtils.js
|
||||
export function fmtDate(iso) {
|
||||
if (!iso) return '';
|
||||
return new Date(iso).toLocaleDateString('ko-KR', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: GREEN 확인** — Run: `npx vitest run src/pages/insta/instaUtils.test.js`. Expected: PASS(2 tests).
|
||||
|
||||
- [ ] **Step 5: hooks/usePollTask.js 작성** — 현 `InstaCards.jsx`의 `usePollTask`(45–82) 함수 본문 verbatim 이동, `import { useState, useEffect, useRef } from 'react';`(실제 사용 훅) + `import { getInstaTask } from '../../../api';` + `export default function usePollTask(onDone) { ... }`. 내부 `getInstaTask` 참조 그대로.
|
||||
|
||||
- [ ] **Step 6: components/StatusBadge.jsx 작성** — 현 `StatusBadge`(36–42) verbatim: `import React from 'react'; const StatusBadge = ({ status }) => (<span className={\`ic-status-badge ic-status-badge--${status || 'draft'}\`}>{status || 'draft'}</span>); export default StatusBadge;` (JSX는 원본과 동일하게).
|
||||
|
||||
- [ ] **Step 7: components/TaskStatusBox.jsx 작성** — 현 `TaskStatusBox`(84–99) verbatim 이동. `import React from 'react';` + 원본이 참조하는 심볼(예: 상태 텍스트/StatusBadge? — 원본 본문 확인해 실제 참조만 import; 순수 표현이면 React만). `export default TaskStatusBox;`.
|
||||
|
||||
- [ ] **Step 8: InstaCards.jsx 수정** — `fmtDate`/`usePollTask`/`StatusBadge`/`TaskStatusBox` 인라인 정의 4개 제거. 상단에 추가:
|
||||
```jsx
|
||||
import { fmtDate } from './instaUtils';
|
||||
import usePollTask from './hooks/usePollTask';
|
||||
import StatusBadge from './components/StatusBadge';
|
||||
import TaskStatusBox from './components/TaskStatusBox';
|
||||
```
|
||||
아직 인라인인 컴포넌트(TriggerPanel/ExternalTrendsPanel/SlatesPanel/SlateDetail/PromptTemplatesEditor)가 이 심볼들을 참조 → 동일 모듈 scope에서 import된 심볼 사용(동작 보존). api import 블록에서 이제 shell 파일 어디서도 안 쓰는 것이 있으면 제거(단 아직 인라인 컴포넌트가 쓰는 것은 유지). `getInstaTask`는 usePollTask로 이동했지만 shell `handleCreateSlate`가 여전히 직접 사용 → 유지.
|
||||
|
||||
- [ ] **Step 9: 검증** — Run: `npm run test:run`(전체 green, ≥165+2=167), `npm run lint`(0 에러), `npm run build`(`✓ built`).
|
||||
|
||||
- [ ] **Step 10: Commit**
|
||||
```bash
|
||||
git add src/pages/insta/instaUtils.js src/pages/insta/instaUtils.test.js src/pages/insta/hooks/usePollTask.js src/pages/insta/components/StatusBadge.jsx src/pages/insta/components/TaskStatusBox.jsx src/pages/insta/InstaCards.jsx
|
||||
git commit -m "refactor(insta): instaUtils(fmtDate)+usePollTask 훅+StatusBadge/TaskStatusBox 추출"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Cards 탭 좌측 — TriggerPanel + KeywordsPanel
|
||||
|
||||
**Files:**
|
||||
- Create: `src/pages/insta/components/TriggerPanel.jsx`, `src/pages/insta/components/KeywordsPanel.jsx`
|
||||
- Modify: `src/pages/insta/InstaCards.jsx`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `usePollTask`(Task1), `TaskStatusBox`(Task1).
|
||||
- Produces: `TriggerPanel()`, `KeywordsPanel({ onCreateSlate })` (default export).
|
||||
|
||||
- [ ] **Step 1: components/TriggerPanel.jsx 작성** — 현 `TriggerPanel`(471–521) 함수 본문 verbatim 이동. imports:
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import usePollTask from '../hooks/usePollTask';
|
||||
import TaskStatusBox from './TaskStatusBox';
|
||||
import { instaCollectNews, instaExtractKeywords } from '../../../api';
|
||||
```
|
||||
내부 `usePollTask()`×2, `TaskStatusBox`, `instaCollectNews`/`instaExtractKeywords` 참조 그대로. `export default TriggerPanel;`.
|
||||
|
||||
- [ ] **Step 2: components/KeywordsPanel.jsx 작성** — 현 `KeywordsPanel`(527–637) 함수 본문 verbatim 이동 + **상수 `CATEGORIES`(524)·`KEYWORDS_PER_PAGE`(525)를 이 파일 상단에 co-locate**. imports:
|
||||
```jsx
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { getInstaKeywords } from '../../../api';
|
||||
```
|
||||
(실제 사용 훅/헬퍼만 — 원본이 useState/useEffect/useCallback/useMemo와 getInstaKeywords, onCreateSlate prop 사용.) 상수:
|
||||
```jsx
|
||||
const CATEGORIES = ['전체', 'economy', 'psychology', 'celebrity'];
|
||||
const KEYWORDS_PER_PAGE = 10;
|
||||
```
|
||||
`export default KeywordsPanel;`.
|
||||
|
||||
- [ ] **Step 3: InstaCards.jsx 수정** — `TriggerPanel`/`KeywordsPanel` 인라인 정의 + `CATEGORIES`/`KEYWORDS_PER_PAGE` 상수 제거. 추가:
|
||||
```jsx
|
||||
import TriggerPanel from './components/TriggerPanel';
|
||||
import KeywordsPanel from './components/KeywordsPanel';
|
||||
```
|
||||
이제 shell에서 안 쓰는 api import 제거: `instaCollectNews`/`instaExtractKeywords`(TriggerPanel로 이동), `getInstaKeywords`(KeywordsPanel로 이동)가 shell 다른 곳에서 안 쓰이면 shell import에서 삭제. `TaskStatusBox` shell import는 아직 인라인 ExternalTrendsPanel(245)이 사용 → **유지**. `usePollTask` shell import는 이제 shell 인라인에서 미사용(TriggerPanel 이동) → **제거**.
|
||||
|
||||
- [ ] **Step 4: 검증** — `npm run test:run`(green), `npm run lint`(0), `npm run build`(✓).
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
```bash
|
||||
git add src/pages/insta/components/TriggerPanel.jsx src/pages/insta/components/KeywordsPanel.jsx src/pages/insta/InstaCards.jsx
|
||||
git commit -m "refactor(insta): TriggerPanel/KeywordsPanel 추출 (Cards 탭 좌측)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Cards 탭 우측 (중첩 트리) — SlatesPanel + SlateDetail + PagesStrip
|
||||
|
||||
**Files:**
|
||||
- Create: `src/pages/insta/components/SlatesPanel.jsx`, `src/pages/insta/components/SlateDetail.jsx`, `src/pages/insta/components/PagesStrip.jsx`
|
||||
- Modify: `src/pages/insta/InstaCards.jsx`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `StatusBadge`(Task1), `fmtDate`(Task1).
|
||||
- Produces: `SlatesPanel({ selectedId, onSelect })`, `SlateDetail({ slate, onDelete, onRender })`, `PagesStrip({ slateId, pageCount })` (default export).
|
||||
|
||||
- [ ] **Step 1: components/PagesStrip.jsx 작성** — 현 `PagesStrip`(733–817) verbatim 이동. imports: `import React, { useState, useCallback, useEffect, useRef } from 'react';`(실제 사용) + `import { getInstaAssetUrl } from '../../../api';`. `export default PagesStrip;`.
|
||||
|
||||
- [ ] **Step 2: components/SlateDetail.jsx 작성** — 현 `SlateDetail`(818–940) verbatim 이동. imports:
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import StatusBadge from './StatusBadge';
|
||||
import PagesStrip from './PagesStrip';
|
||||
import { getInstaAssetUrl, instaPackageUrl } from '../../../api';
|
||||
```
|
||||
(실제 사용 심볼만 — 원본 본문이 참조하는 것으로 확정; React 훅 사용 시 추가.) 내부 `<StatusBadge>`(832)·`<PagesStrip>`(845) 참조 그대로. `export default SlateDetail;`.
|
||||
|
||||
- [ ] **Step 3: components/SlatesPanel.jsx 작성** — 현 `SlatesPanel`(638–732) verbatim 이동. imports:
|
||||
```jsx
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import StatusBadge from './StatusBadge';
|
||||
import SlateDetail from './SlateDetail';
|
||||
import { fmtDate } from '../instaUtils';
|
||||
import { getInstaSlates, getInstaSlate, getInstaAssetUrl, renderInstaSlate, deleteInstaSlate } from '../../../api';
|
||||
```
|
||||
(실제 사용만 — 원본이 `<StatusBadge>`(711)·`<SlateDetail>`(722)·`fmtDate`(710)·slate 목록/상세/렌더/삭제 api 사용. onDelete/onRender 핸들러가 renderInstaSlate/deleteInstaSlate를 어디서 호출하는지 원본 따라 배치.) `export default SlatesPanel;`.
|
||||
|
||||
- [ ] **Step 4: InstaCards.jsx 수정** — `SlatesPanel`/`SlateDetail`/`PagesStrip` 인라인 정의 제거. 추가: `import SlatesPanel from './components/SlatesPanel';`. (SlateDetail/PagesStrip은 shell이 직접 렌더 안 함 → shell import 아님.) 이제 shell에서 미사용: `StatusBadge` import(SlatesPanel/SlateDetail 이동으로 shell 인라인 미사용) → **제거**; 관련 api import(getInstaSlates/getInstaSlate/getInstaAssetUrl/renderInstaSlate/deleteInstaSlate/instaPackageUrl 등)가 shell 인라인에서 미사용이면 **제거**.
|
||||
|
||||
- [ ] **Step 5: 검증** — `npm run test:run`(green), `npm run lint`(0), `npm run build`(✓).
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
```bash
|
||||
git add src/pages/insta/components/SlatesPanel.jsx src/pages/insta/components/SlateDetail.jsx src/pages/insta/components/PagesStrip.jsx src/pages/insta/InstaCards.jsx
|
||||
git commit -m "refactor(insta): SlatesPanel/SlateDetail/PagesStrip 추출 (Cards 탭 우측)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Trends 탭 3패널 + PromptTemplatesEditor + shell 확정
|
||||
|
||||
**Files:**
|
||||
- Create: `src/pages/insta/components/AccountFocusPanel.jsx`, `src/pages/insta/components/ExternalTrendsPanel.jsx`, `src/pages/insta/components/PreferenceImpactPanel.jsx`, `src/pages/insta/components/PromptTemplatesEditor.jsx`
|
||||
- Modify: `src/pages/insta/InstaCards.jsx`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `TaskStatusBox`(Task1), `fmtDate`(Task1).
|
||||
- Produces: `AccountFocusPanel()`, `ExternalTrendsPanel({ onCreateSlate })`, `PreferenceImpactPanel()`, `PromptTemplatesEditor()` (default export).
|
||||
|
||||
- [ ] **Step 1: components/AccountFocusPanel.jsx 작성** — 현 `AccountFocusPanel`(101–169) verbatim 이동. imports: `import React, { useState, useCallback, useEffect } from 'react';` + `import { getInstaPreferences, putInstaPreferences } from '../../../api';`(실제 사용). `export default AccountFocusPanel;`.
|
||||
|
||||
- [ ] **Step 2: components/ExternalTrendsPanel.jsx 작성** — 현 `ExternalTrendsPanel`(175–267) verbatim 이동 + **상수 `CATEGORY_COLORS`(170)를 이 파일 상단에 co-locate**. imports:
|
||||
```jsx
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import TaskStatusBox from './TaskStatusBox';
|
||||
import { fmtDate } from '../instaUtils';
|
||||
import { getInstaTrends, instaCollectTrends, getInstaTask } from '../../../api';
|
||||
```
|
||||
(실제 사용만 — 원본이 `<TaskStatusBox>`(245)·`fmtDate`(238)·`CATEGORY_COLORS`(221,252)·trends api·인라인 폴링 getInstaTask 사용.) 상수 `const CATEGORY_COLORS = { ... };`(원본 170 블록 verbatim). `export default ExternalTrendsPanel;`.
|
||||
|
||||
- [ ] **Step 3: components/PreferenceImpactPanel.jsx 작성** — 현 `PreferenceImpactPanel`(268–300) verbatim 이동. imports: `import React, { useState, useEffect } from 'react';` + `import { getInstaPreferences } from '../../../api';`(실제 사용). `export default PreferenceImpactPanel;`.
|
||||
|
||||
- [ ] **Step 4: components/PromptTemplatesEditor.jsx 작성** — 현 `PromptTemplatesEditor`(943–1013) verbatim 이동 + **상수 `PROMPT_NAMES`(941)를 이 파일 상단에 co-locate**. imports:
|
||||
```jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { fmtDate } from '../instaUtils';
|
||||
import { getInstaPrompt, putInstaPrompt } from '../../../api';
|
||||
```
|
||||
(실제 사용만 — 원본이 `fmtDate`(984)·prompt api·`PROMPT_NAMES` 사용.) 상수 `const PROMPT_NAMES = ['slate_writer', 'category_seeds'];`. `export default PromptTemplatesEditor;`.
|
||||
|
||||
- [ ] **Step 5: InstaCards.jsx 최종 확정(shell)** — 4개 인라인 정의 + `CATEGORY_COLORS`/`PROMPT_NAMES` 상수 제거. 추가:
|
||||
```jsx
|
||||
import AccountFocusPanel from './components/AccountFocusPanel';
|
||||
import ExternalTrendsPanel from './components/ExternalTrendsPanel';
|
||||
import PreferenceImpactPanel from './components/PreferenceImpactPanel';
|
||||
import PromptTemplatesEditor from './components/PromptTemplatesEditor';
|
||||
```
|
||||
**shell import 최종 정리** — shell이 실제 쓰는 것만 남긴다:
|
||||
- React: `import React, { useState, useEffect, useCallback } from 'react';`(shell이 실제 사용하는 훅만; useRef/useMemo는 shell 미사용이면 제거)
|
||||
- `import PullToRefresh from '../../components/PullToRefresh';`(유지)
|
||||
- 7 컴포넌트 import(TriggerPanel/KeywordsPanel/SlatesPanel/PromptTemplatesEditor/AccountFocusPanel/ExternalTrendsPanel/PreferenceImpactPanel)
|
||||
- api: shell이 직접 쓰는 것만 — `import { getInstaStatus, createInstaSlate, getInstaTask } from '../../../api';`(loadStatus=getInstaStatus, handleCreateSlate=createInstaSlate+getInstaTask). 나머지 api·`TaskStatusBox`·`StatusBadge`·`usePollTask`·`fmtDate` import 전부 제거(모두 컴포넌트로 이동).
|
||||
- `import './InstaCards.css';`(유지)
|
||||
결과 `InstaCards.jsx`는 shell(~140줄).
|
||||
|
||||
- [ ] **Step 6: 검증** — Run: `npm run test:run`(전체 green, 회귀 0), `npm run lint`(0 에러, 미사용 import 0), `npm run build`(`✓ built`), `wc -l src/pages/insta/InstaCards.jsx`(~140줄 확인).
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
```bash
|
||||
git add src/pages/insta/components/AccountFocusPanel.jsx src/pages/insta/components/ExternalTrendsPanel.jsx src/pages/insta/components/PreferenceImpactPanel.jsx src/pages/insta/components/PromptTemplatesEditor.jsx src/pages/insta/InstaCards.jsx
|
||||
git commit -m "refactor(insta): Trends 3패널+PromptTemplatesEditor 추출 → InstaCards shell 확정"
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Manual verification (dev server)** — `npm run dev` → `http://localhost:3007/insta`. 확인: 2탭(Cards/Trends) 전환 + URL `?tab=trends` 동기화 / 트리거(뉴스 수집·키워드 추출 버튼 + 스피너 + TaskStatusBox) / 키워드 목록·카테고리·페이지네이션 / 슬레이트 생성(키워드 클릭 → progress 배너 → 완료 시 Cards 탭 자동 전환 + 상세) / 슬레이트 목록·상세(SlateDetail)·페이지 스트립(PagesStrip 스크롤·키보드)·삭제·렌더 / 프롬프트 편집(저장) / Trends 3패널(AccountFocus·ExternalTrends 수집·PreferenceImpact)이 리팩토링 전과 **동일 동작**.
|
||||
|
||||
---
|
||||
|
||||
## Self-Review 결과
|
||||
|
||||
**Spec coverage** (설계 §1–§7):
|
||||
- §3 file map(instaUtils/usePollTask/11 컴포넌트/shell) → Task 1(utils+hook+2리프) / Task 2(2) / Task 3(3) / Task 4(4+shell) ✅ (11 = 2+2+3+4)
|
||||
- §4 shell 책임(탭·progress·handleCreateSlate 유지, 7 컴포넌트 조합) → Task 4 Step 5 ✅
|
||||
- §5 테스트(instaUtils fmtDate 유닛 + build/lint/전체/수동) → Task 1 Step 1–4 + 각 task 검증 + Task 4 Step 8 ✅
|
||||
- §6 완료기준 → 각 task 검증 + Task 4 line count ✅
|
||||
- §3 상수 배치(CATEGORIES/KEYWORDS_PER_PAGE→KeywordsPanel, PROMPT_NAMES→PromptTemplatesEditor, CATEGORY_COLORS→ExternalTrendsPanel co-locate, fmtDate→instaUtils) → 각 Task 명시 ✅
|
||||
|
||||
**Placeholder scan:** fmtDate 테스트/구현 완전 명시. 리프트 컴포넌트는 "현 정의(이름·line 힌트) verbatim + 명시 import/export/상수 co-locate"로 지정(원본 코드 존재 → 재현 대신 위치+연결 지정, 서브프로젝트 1~3 동일 컨벤션). 일부 컴포넌트 import는 "원본이 참조하는 심볼만(build/lint 검출)"으로 위임 — 리프트 특성상 정확 목록은 원본 참조와 동일. ✅
|
||||
|
||||
**Type consistency:** usePollTask 반환 `{ taskId, task, start, clear }` ↔ TriggerPanel 사용 일치. 컴포넌트 props(status/task/onCreateSlate/selectedId/onSelect/slate/onDelete/onRender/slateId/pageCount) ↔ shell/부모 전달 일치. 경로(components→`../../../api`·`../instaUtils`·`./sibling`·`../hooks/usePollTask`) 일관. StatusBadge/TaskStatusBox는 Task1 추출 → 소비처(Task2/3/4)가 직접 import, shell은 소비 인라인 남아있는 동안만 브릿지 import 유지 후 제거. ✅
|
||||
|
||||
**참고:** Task 순서 의존(Task1 리프/훅/유틸 → Task2/3/4가 import). behavior-preserving라 컴포넌트 RED는 "신규 파일 미존재"로 성립, GREEN은 추출 후 전체 회귀 0. fmtDate만 명시적 TDD(RED→GREEN).
|
||||
Reference in New Issue
Block a user