115 KiB
AI 캐릭터 관리자 웹 구현 계획
For agentic workers: REQUIRED SUB-SKILL: Use
superpowers:subagent-driven-development(recommended) orsuperpowers:executing-plansto implement this plan Phase-by-Phase. 모든 구현 항목은 체크박스(- [ ])로 추적한다.
Goal: ADMIN이 로그인한 뒤 AI 캐릭터를 선택하고, 선택한 캐릭터 문맥에서 Character·Audio·Series·Community·FanTalk·Comments를 안전하게 관리하는 독립 React SPA를 구현한다.
Architecture: 프로젝트 세팅과 공통 플랫폼·인증/인가를 먼저 완결한 뒤, 각 도메인을 API·상태·화면·오류·반응형·접근성·E2E까지 포함한 vertical slice로 구현한다. 모든 하위 리소스는 URL의 characterId를 기준으로 격리하고, 공통 API client가 envelope parsing, 인증 header, Accept-Language: ko, 401/403을 담당한다. 두 개 이상의 Phase에서 동일한 의미로 반복될 것이 확정된 UI·파일·미디어 컴포넌트는 Phase 1에서 먼저 만들고, 그 밖의 UI는 도메인 Phase 안에서 작게 나눈 뒤 실제 재사용 근거가 생길 때 shared로 올린다.
Tech Stack: React, TypeScript, Vite, Tailwind CSS, shadcn/ui, React Router, TanStack Query, React Hook Form, Zod, Axios/XHR upload adapter, date-fns/date-fns-tz, dnd-kit, Lucide React, Vitest, React Testing Library, MSW, Playwright, axe-core.
| 문서 항목 | 내용 |
|---|---|
| 상태 | Phase 0~1 완료, Phase 2 착수 전 |
| 최초 작성일 | 2026-07-25 |
| 재작성일 | 2026-07-26 |
| 요구사항 기준 | prd.md |
| API 기준 | api-contract.md |
1. 전역 제약
- 이번 단계에서는 이 계획 문서만 수정한다. 애플리케이션 코드와 프로젝트 설정은 후속 구현 단계에서 변경한다.
- PRD와 최초 API Contract가 충돌하면 PRD
11.4 API 계약 보정사항을 우선한다. - 로그인은
POST /admin/member/login, 로그아웃은 body 없는POST /member/logout을 사용한다. - JWT와 ADMIN role은
sessionStorage에만 저장한다. refresh token과 자동 갱신은 구현하지 않는다. - 모든 요청에
Accept-Language: ko를 보내고, 로그인 이외의 보호 요청과 로그아웃에 Bearer token을 보낸다. externalCharacterId는 type, DTO, payload, fixture, UI에 만들지 않는다.- 모든 생성 payload에는
isActive를 넣지 않는다. - Character·Audio·Series·Community 일반 수정에는
isActive를 넣지 않고 soft delete에만isActive=false를 보낸다.isActive=true, 복원, hard delete는 구현하지 않는다. - Character·Audio·Series·Community 목록은 active-only 서버 응답을 사용한다. 활성 상태 query나 client-side 활성 필터를 추가하지 않는다.
- Character·Audio·Series soft delete 성공 후 해당 목록으로 이동한다. Community는 열린 Sheet를 닫고 현재 목록에서 제거한다. 모두 성공 알림을 표시한다.
- 워크스페이스 상세 성공 응답의 Character가
isActive=false이면 모든 하위 mutation 진입점을 차단한다. soft delete 직후에는 목록 이동을 우선한다. - Series 생성 payload에는
state를 넣지 않는다. 수정에서 state를 바꾸지 않으면 key를 생략한다. - Series state에
OPEN, 요일에MONDAY같은 보정 전 enum을 사용하지 않는다. - multipart의 JSON part 이름은
request로 고정하고, optional 교체 파일을 보내지 않으면 기존 media를 유지한다. - 모든 일반 목록은 server pagination을 사용한다. 검색을 제공하는 목록은 약 300ms debounce를 일관되게 적용하고 URL query와 기존 화면 데이터를 유지한다.
- image 영역은 비율과 크기를 예약하고 목록 image는 lazy load한다. 날짜·가격 공통 formatter는 Phase 1에서 만들고 상태 label은 각 도메인이
StatusBadge에 주입한다. - 모바일 기능 범위는 PRD
9를 각 도메인 Phase에서 함께 구현한다. 반응형 정책을 마지막에 덧붙이지 않는다. - 초기 릴리스는 밝은 테마만 제공한다. main/primary는
#00BDF7, primary foreground는#062B36, 흰 배경의 link/focus ring은#007EA8이다. - JWT, password, signed URL, 업로드 파일 본문을 console, 분석 이벤트, 오류 리포트, 영구 저장소에 기록하지 않는다.
- 제공 계약에 없는 문자열·배열 최대값, price 상한, 오류 status/key를 추정하지 않는다.
- 새 dependency는 해당 Phase에서 실제로 필요한 최소 항목만 추가한다. shadcn component와 폴더도 소비 시점에 생성한다.
- Page는 routing·query·permission·component 조합만 담당한다. 도메인 표시와 상호작용은 feature component로, 두 Phase 이상에서 의미와 동작이 같은 것은 shared component로 분리한다.
- 단순 markup 한 조각, 한 화면 전용 UI, 서로 다른 도메인 규칙을 하나의 범용 prop API로 합치기 위한 component는 만들지 않는다.
- 모든 기능은 실패하는 test를 먼저 만들고 최소 구현으로 통과시킨다.
2. Phase 운영 규칙
2.1 독립 검증 규칙
각 Phase는 다음 결과를 모두 가진다.
- 사용자가 직접 확인할 수 있는 하나 이상의 완결된 흐름
- 해당 Phase가 소유하는 API contract test
- loading·empty·error·success 상태
- 해당 viewport 범위와 keyboard·접근성 검증
- Phase 전용 E2E와 공통 typecheck·lint·build 결과
각 Task는 다음 Red/Green loop를 따른다.
- Task에 적힌 test file에 가장 작은 실패 test를 추가한다.
- 해당 test만 실행해 의도한 assertion 실패인지 확인한다.
- 그 test를 통과시키는 최소 구현을 작성한다.
- 관련 feature test 전체를 실행한다.
- refactor 후 typecheck·lint와 Phase E2E를 다시 실행한다.
import 오류, test 환경 오류, 임시 mock 누락 같은 우발적 실패는 Red 증거로 인정하지 않는다.
2.2 컴포넌트 조합 규칙
각 도메인 Phase는 코드를 작성하기 전에 다음 component map을 해당 Phase의 주요 Files와 checklist에 반영한다.
| 분류 | 책임 | 예시 |
|---|---|---|
| Page | route param/query, data loading, permission, navigation, component 조합 | CharacterListPage, AudioContentDetailPage |
| Feature component | 한 도메인의 표시·입력·상호작용 규칙 | CharacterForm, PublishedDaysField |
| Shared component | 두 개 이상 Phase에서 같은 의미·동작으로 재사용 | PageState, ConfirmDeactivateDialog, ImageCropDialog |
| shadcn primitive | 접근 가능한 저수준 control | Button, Dialog, Table, Form |
- 화면별 loading·empty·error·success·read-only·mobile 상태와 주요 action을 먼저 inventory한다.
- Page에 큰 JSX와 form/media 로직을 직접 쌓지 않고, 독립 test가 가능한 feature/shared component를 조합한다.
- 기존 shadcn/shared component로 표현할 수 있으면 새 wrapper를 만들지 않는다.
- shared component는 domain DTO나 endpoint를 import하지 않고 controlled value, slot, callback으로 조합한다.
- 재사용을 위해 boolean prop를 계속 늘리기보다 작은 component와 composition을 사용한다.
- component map과 실제 화면 구성이 달라지면 구현 전에 이 계획의
주요 Files를 먼저 갱신한다.
2.3 미결·외부 의존 처리
전역 Backend Contract Phase를 만들지 않는다. 각 도메인 Phase의 첫 Task에서 그 도메인에 필요한 계약만 확인한다.
| 분류 | 처리 규칙 |
|---|---|
| 계약이 제공됨 | api-contract.md에 request/response/error 예시를 반영하고 contract test를 만든 뒤 구현한다. |
| 안전한 확정 기본값이 있음 | 문서에 적힌 최소 규칙만 구현한다. 예: price 상한 미제공 시 0 이상 정수만 검증한다. |
| 계약 없이 안전하게 구현할 수 없음 | endpoint·DTO·오류를 추측하지 않는다. 해당 최소 기능 또는 Phase를 현재 릴리스에서 제외하기 전에 PRD 결정 기록, API Contract, 이 계획을 함께 갱신한다. |
| 구현 중 불필요하다고 판단 | 활성 체크 항목을 제거하되 PRD 결정 기록에 삭제 이유와 날짜를 남긴다. 과거 결정 기록은 지우지 않는다. |
| 계약이 후속 도착 | 완료한 Phase를 묵시적으로 다시 열지 않고 별도 후속 vertical slice를 계획한다. |
OQ-009는 각 도메인의 실제 폼을 만든 시점에 한 번만 판단한다. 최대값이 필요하면 backend 호환 확인 후 PRD·API Contract·schema·경계 test를 같은 변경에서 갱신한다. 필요 없으면 “상한 추가 없음”으로 종결하고 관련 구현 항목을 삭제한다.OQ-010감사 로그 조회 UI는 현재 릴리스 구현 항목을 만들지 않는다. 포함하기로 바뀌면 backend 조회 계약을 포함한 별도 Phase로 다시 계획한다.- P0 외부 의존이 남아 있으면 영향을 받는 network flow를 완료로 표시하지 않는다. 다른 독립 Phase는 계속 진행할 수 있다.
- 이미지 최대
10MB의 정확한 byte 경계처럼 표현만으로 단일 값이 정해지지 않는 항목은 첫 파일 Phase에서 결정 기록과 contract를 먼저 보정한다.
2.4 Goal 기능 운영 규칙
create_goal에는 동시에 하나의 미완료 goal만 등록한다. Phase 전체가 아니라 아래에Goal 실행으로 표시한 Task 하나를 기본 단위로 사용한다.- goal objective는 해당 Task의
Goal 실행,시작 조건,완료 증거,범위 밖을 함께 복사해 등록한다. 사용자가 명시적으로 요청하지 않으면 token budget을 설정하지 않는다. - 활성 goal이 있으면 새 goal을 만들지 않고 같은 Task를 이어서 수행한다. 체크박스 일부만 끝났거나 검증·기록이 남았으면 goal을 완료 처리하지 않는다.
완료 증거와 해당 Task의 체크박스를 모두 충족하고plan-task.md검증 기록까지 누적한 뒤에만 goal을complete로 갱신한다.- 외부 계약이나 권한 같은 동일 차단 사유가 최초 시도와 자동 후속을 포함해 3회 연속 반복되고, 문서화·독립 작업 등 의미 있는 진전도 불가능할 때만 goal을
blocked로 갱신한다. 그 전에는 가능한 범위를 계속 수행한다. - 계약 미제공으로 기능을 제외할 때는 PRD 결정 기록 →
api-contract.md→ 이 계획의 활성 checklist 순서로 갱신해야 한다. 이 문서화가 끝나기 전에는 goal을 완료 처리하지 않는다. - 각 Phase는 자신의 Task goal을 번호 순서로 완료한 뒤 Phase Gate를 마지막 goal로 실행한다. Gate goal은 실제 명령 결과와 수동 검증 결과를 기록한 뒤 완료한다.
- Phase 0~1처럼 이미 완료 체크된 범위는 새 구현 goal로 다시 만들지 않는다. 회귀나 기록 정합성 보정이 필요하면 별도 수정 goal을 만들고 기존 검증 기록을 덮어쓰지 않는다.
Goal objective 권장 형식:
[Goal ID]의 목표를 plan-task.md에 정의된 범위로 수행한다.
시작 조건과 전역 제약을 확인하고, 체크박스를 Red/Green loop로 완료한다.
완료 증거의 검증과 문서 기록까지 끝나기 전에는 complete로 표시하지 않는다.
범위 밖 항목은 구현하지 않으며, 외부 계약은 추정하지 않는다.
3. Phase 지도
| Phase | 결과 | 선행조건 | 독립 검증 핵심 |
|---|---|---|---|
| 0 | 프로젝트 세팅 | 없음 | fresh install, root smoke, unit/E2E/build |
| 1 | 공통 플랫폼·인증/인가·컴포넌트 기반 | Phase 0 | shared component contract + login → protected shell → refresh restore → logout/401/403 |
| 2 | Character workspace | Phase 1 | list/search → create/select → detail/edit → deactivate |
| 3 | Audio vertical slice | Phase 2의 workspace core | list/filter/detail/play → create/edit/upload → deactivate |
| 4 | Series vertical slice | Phase 3의 Audio 조회 API | CRUD → content link/unlink → full reorder |
| 5 | Community vertical slice | Phase 3의 media/file primitive | list → collection Sheet edit/pin → media play → deactivate |
| 6 | FanTalk vertical slice | Phase 2 | list/filter/detail → one reply → edit |
| 7 | Comments vertical slice | Phase 3 + Phase 5 | Audio/Community thread → permission별 CRUD |
| 8 | 교차 회귀·인수인계 | 활성 범위의 Phase 0~7 | 전체 journey, viewport, axe, security, build |
기본 진행 순서는 Phase 번호를 따른다. 다만 Phase 4·5·6은 자신의 선행조건과 계약이 충족되면 병행할 수 있고, 외부 계약으로 막힌 Phase가 다른 독립 Phase를 막지 않는다.
Phase 0 Setup
└─ Phase 1 Platform + Auth/Authz + Shared Components
└─ Phase 2 Character Workspace
├─ Phase 3 Audio ──┬─ Phase 4 Series
│ └─ Phase 5 Community ──┐
└─ Phase 6 FanTalk ├─ Phase 8 Final
Phase 3 + Phase 5 ── Phase 7 Comments ┘
4. 파일 책임 지도
src/
app/ # entry, providers, router, route constants
components/ui/ # 실제로 추가한 shadcn primitive만 보관
features/
auth/
characters/
audio-contents/
series/
community-posts/
fan-talks/
comments/
layouts/ # Admin shell, Character workspace
shared/
api/ # envelope, client, query, pagination, errors
config/ # runtime env
hooks/ # 공통 query/form interaction hook
lib/ # 공통 formatter/media helper
test/ # MSW, render helper, fixtures
ui/ # Phase 1에서 확정한 공통 UI와 후속 검증된 추출물
validation/ # 공통 file/media policy
styles/
tests/e2e/
feature 내부의 api/, components/, model/, pages/, schemas/, tests/는 실제 파일이 생길 때만 만든다. 한 구현만을 위한 interface, factory, registry는 만들지 않는다. 모든 Page는 해당 feature의 component와 shared component를 조합해 구성한다.
Phase 0. 프로젝트 세팅
목표: 비즈니스 기능 없이도 동일한 명령으로 개발·test·build할 수 있는 React SPA 기반을 만든다.
Phase Goal P0: Task 0.1 → 0.2 → Phase 0 Gate 순서로 재현 가능한 SPA 기반을 완성한다.
- 시작 조건: 없음.
- 완료 조건:
P0-T1,P0-T2,P0-GATE완료와 fresh install 검증 기록. - 현재 상태: 완료. 재검증은 기존 goal을 다시 열지 않고 별도 회귀 수정 goal로 수행한다.
주요 Files:
- Create:
package.json,package-lock.json - Create:
index.html,vite.config.ts - Create:
tsconfig.json,tsconfig.app.json,tsconfig.node.json - Create:
eslint.config.js - Create:
.env.example,README.md - Create:
playwright.config.ts - Create:
src/main.tsx,src/app/App.tsx,src/app/App.test.tsx - Create:
src/shared/config/env.ts,src/shared/config/env.test.ts - Create:
src/shared/test/setup.ts - Create:
tests/e2e/smoke.spec.ts
Task 0.1 런타임·패키지 기반
Goal 실행 P0-T1: Node/npm, Vite/TypeScript 진입점, runtime env와 최소 script를 재현 가능하게 구성한다.
-
시작 조건: 지원 Node 버전과 환경 변수 기준 확인.
-
완료 증거: 체크박스 전체, env/App focused test, typecheck·lint·개발/프로덕션 build 및 검증 기록.
-
범위 밖: router, server-state, form, drag-and-drop, 도메인 기능.
-
mise.toml의 Node24.12.0을 기준으로 npm package와 lockfile을 생성한다. -
React + TypeScript + Vite 진입점과
@path alias를 구성한다. -
VITE_API_BASE_URL만.env.example에 문서화하고 token·password 같은 비밀값을 넣지 않는다. -
runtime env 누락·잘못된 URL을 앱 시작 전에 설명 가능한 오류로 차단하는 test를 작성한다.
-
unit test는
vi.stubEnv, Playwright webServer는 명시적 test URL로VITE_API_BASE_URL을 주입해.env.example자동 로드를 전제하지 않는다. -
dev,build,typecheck,lint,test,test:run,e2escript를 정의한다. -
이 Phase에 필요하지 않은 router, server-state, form, drag-and-drop dependency는 아직 설치하지 않는다.
Task 0.2 test 기반
Goal 실행 P0-T2: unit·DOM·E2E test 기반과 접근 가능한 최소 App shell을 완성한다.
-
시작 조건:
P0-T1완료. -
완료 증거: 체크박스 전체, root unit test와 4개 Playwright project smoke 및 검증 기록.
-
범위 밖: 인증과 실제 도메인 route.
-
Vitest, jsdom, React Testing Library, jest-dom을 구성한다.
-
각 test 뒤 DOM·mock·storage가 정리되는 공통 setup을 만든다.
-
Playwright에 desktop Chromium/WebKit과 mobile Chrome/Safari viewport project, Vite webServer를 구성한다.
-
fresh environment에서 Chromium/WebKit browser binary를 설치하는 명령을 README와 Gate에 포함한다.
-
<html lang="ko">,mainlandmark, root content를 확인하는 unit test를 먼저 실패시킨 뒤 최소 App shell을 만든다. -
동일 shell이 각 Playwright project에서 열리는 smoke E2E를 만든다.
Phase 0 Gate
Goal 실행 P0-GATE: Phase 0의 fresh install·unit·E2E·build 재현성을 최종 판정한다.
- 시작 조건:
P0-T1,P0-T2완료. - 완료 증거: 아래 명령 모두 0 exit code, Expected와 실제 결과의 검증 기록.
- 범위 밖: 실패와 무관한 기능 추가. 실패는 소유 Task 수정 goal로 되돌린다.
npm ci
npx playwright install chromium webkit
npm run typecheck
npm run lint
npm run test:run -- src/app/App.test.tsx src/shared/config/env.test.ts
npm run e2e -- tests/e2e/smoke.spec.ts
npm run build:dev
npm run build:prod
Expected: fresh install 후 typecheck·lint·unit·E2E·production build가 모두 0 exit code이며 환경 변수 오류가 test로 고정된다.
1차 구현 검증 기록 (2026-07-26):
- 무엇을: Phase 0의 React + TypeScript + Vite 프로젝트 기반, runtime env 검증, Vitest/RTL/jsdom, Playwright Chromium/WebKit 및 mobile viewport smoke, README와 npm scripts를 구현했다.
- 왜: 후속 Phase가 동일한 명령으로 개발·test·build할 수 있고,
VITE_API_BASE_URL누락/오류와 root shell 회귀를 자동 검증하기 위해서다. - 어떻게:
npm ci성공(0 vulnerabilities),npx playwright install chromium webkit성공,VITE_API_BASE_URL=http://127.0.0.1:4010 npm run typecheck성공,VITE_API_BASE_URL=http://127.0.0.1:4010 npm run lint성공,VITE_API_BASE_URL=http://127.0.0.1:4010 npm run test:run -- src/app/App.test.tsx src/shared/config/env.test.ts성공(2 files/4 tests passed),VITE_API_BASE_URL=http://127.0.0.1:4010 npm run e2e -- tests/e2e/smoke.spec.ts성공(4 projects passed),VITE_API_BASE_URL=http://127.0.0.1:4010 npm run build성공. - 정정/참고:
@playwright/test@1.62.0의 WebKit binary가 현재 macOS 26.0 x86_64 환경에서 시작 전Segmentation fault: 11로 실패해, Phase 0 Gate 충족을 위해@playwright/test@1.61.1로 고정했다.
2차 설정 정정 기록 (2026-07-26):
- 무엇을: 개발 서버 포트를
8888로 고정하고, 개발/프로덕션 API base URL과 build 명령을 분리했다. - 왜: 실행 환경별 API endpoint를 명확히 분리하고, AGENTS.md를 짧게 유지하면서 상세 지침을
docs/agent-guide/의 세부 문서에서 관리하기 위해서다. - 어떻게:
npm run typecheck,npm run lint,npm run test:run -- src/app/App.test.tsx src/shared/config/env.test.ts,npm run e2e -- tests/e2e/smoke.spec.ts,npm run build:dev,npm run build:prod가 성공했다. - 정정: 기존 1차 기록의
VITE_API_BASE_URL=http://127.0.0.1:4010과 단일npm run build는 당시 실행 기록으로 보존하되, 현재 Gate 기준은.env.development,.env.production,build:dev,build:prod를 사용한다.
Task R0.1 — 테스트 격리와 Phase 0 증거 정합성 복구
Goal 실행 P0-R1: Phase 0 테스트 격리와 루트 셸·Playwright 증거 정합성을 복구하고 회귀를 방지한다.
-
연결 리뷰:
REV-P0-001,REV-P0-002,REV-P0-003 -
시작 조건: review finding을 보존하고 기존 완료 체크를 되돌리지 않는다.
-
완료 증거: stubbed global 복원 회귀 test, root
main과 대표 content assertion, 현재 Playwright mode URL 전략과 실행 가능한 agent-guide 문서 경로 정정 기록, P0-GATE 통과. -
범위 밖: Phase 1 인증 동작 전면 변경, test framework 교체.
-
src/shared/test/setup-isolation.test.ts로vi.stubGlobal누출을 실패 재현한 뒤restoreAllMocks·unstubAllGlobals정리를 추가했다. -
src/app/App.test.tsx와tests/e2e/smoke.spec.ts에서 rootmain안의 로그인 대표 콘텐츠를 검증한다. -
존재하지 않는
docs/agent-guide/README.md참조를 실제docs/agent-guide/세부 문서 관리 방식으로 정정했다. -
P0-GATE를 재실행하고 결과를 아래에 누적했다.
P0-R1 수정 검증 기록 (2026-07-27):
- RED:
npm run test:run -- src/app/App.test.tsx src/features/auth/tests/login-page.test.tsx src/features/auth/tests/auth-session.test.tsx src/shared/lib/crop-image.test.ts src/shared/validation/file-media-policy.test.ts src/shared/ui/__tests__/file-field.test.tsx src/shared/ui/__tests__/admin-audio-player.test.tsx src/shared/ui/__tests__/confirm-deactivate-dialog.test.tsx src/shared/ui/__tests__/unsaved-changes-guard.test.tsx src/shared/ui/__tests__/image-crop-dialog.test.tsx src/shared/test/setup-isolation.test.ts— 기대 실패 확인, 11 files failed / 13 failed / 40 passed. P0-R1 관련으로 stubbed global 복원과 root shell assertion 실패를 확인했다. - GREEN focused:
npm run test:run -- src/app/App.test.tsx src/shared/config/env.test.ts src/shared/test/setup-isolation.test.ts— 성공, 3 files / 18 tests passed. - P0-GATE:
npm ci— 성공, 307 packages, 0 vulnerabilities.npx playwright install chromium webkit— 성공.npm run typecheck— 성공.npm run lint— 성공.npm run test:run -- src/app/App.test.tsx src/shared/config/env.test.ts src/shared/test/setup-isolation.test.ts— 성공, 18 tests passed.npm run e2e -- tests/e2e/smoke.spec.ts— 성공, 4 projects / 4 tests passed.npm run build:dev— 성공.npm run build:prod— 성공. - 참고: 첫 Gate 병렬 실행 중
npm ci가node_modules를 재설치하는 동안 동시에 실행한tsc/playwright명령이 bin 삭제로 실패했다.npm ci완료 후 같은 명령을 재실행해 모두 성공했다.
Phase 1. 공통 플랫폼·인증/인가·컴포넌트 기반
목표: ADMIN 인증 흐름과 보호된 Admin shell을 완결하고, Phase 2 이후 화면이 조합해 사용할 공통 UI·form·file·media component contract를 제공한다.
Phase Goal P1: Task 1.1 → 1.6과 Phase 1 Gate를 완료해 도메인 Phase가 사용할 공통 contract를 제공한다.
- 시작 조건:
P0-GATE완료. - 완료 조건:
P1-T1~P1-T6,P1-GATE완료와 auth/shared contract 검증 기록. - 현재 상태: 완료. 회귀나 기록 정합성 수정은 별도 수정 goal로 수행한다.
요구사항: AUTH-001~013, UX-001~002, PRD 7, 10.1~10.8, 11.1~11.2, 12의 공통 항목.
주요 Files:
- Create:
components.json,src/styles/globals.css - Create:
src/app/providers.tsx,src/app/router.tsx,src/app/route-paths.ts - Create:
src/shared/api/{types,api-error,client,pagination,query-client}.ts - Create:
src/shared/api/__tests__/{client,pagination}.test.ts - Create:
src/shared/test/{server,handlers,render}.ts - Create:
src/shared/hooks/use-search-params-state.ts - Create:
src/shared/ui/{page-state,status-badge,search-toolbar,resource-pagination,responsive-resource-list}.tsx - Create:
src/shared/ui/{confirm-deactivate-dialog,unsaved-changes-guard,file-field,image-crop-dialog,upload-progress}.tsx - Create:
src/shared/ui/{admin-audio-player,audio-playback-provider}.tsx - Create:
src/shared/validation/{file-validation,image-policy,audio-file-policy}.ts - Create:
src/shared/validation/{file-validation,image-policy,audio-file-policy}.test.ts - Create:
src/shared/lib/{crop-image,format-date,format-price}.ts - Create:
src/shared/lib/{crop-image,format-date,format-price}.test.ts - Create:
src/shared/ui/__tests__/{page-state,resource-list,confirm-deactivate-dialog,unsaved-changes-guard}.test.tsx - Create:
src/shared/ui/__tests__/{file-field,image-crop-dialog,upload-progress,admin-audio-player}.test.tsx - Create:
src/features/auth/api/auth-api.ts - Create:
src/features/auth/model/auth-session.tsx - Create:
src/features/auth/model/auth-session-storage.ts - Create:
src/features/auth/schemas/login-schema.ts - Create:
src/features/auth/pages/{LoginPage,AccessDeniedPage}.tsx - Create:
src/features/auth/components/ProtectedRoute.tsx - Create:
src/features/auth/tests/auth-api.test.ts - Create:
src/features/auth/tests/{login-page,protected-route}.test.tsx - Create:
src/layouts/AdminLayout.tsx - Create:
tests/e2e/auth.spec.ts,tests/e2e/accessibility-shell.spec.ts
Phase 1 공통 컴포넌트 소비처 Matrix:
| Shared component | 확정 소비 Phase | 공통 책임 |
|---|---|---|
PageState |
1~7 | loading·empty·error·retry |
StatusBadge |
2~6 | semantic tone과 label/icon slot |
SearchToolbar |
2, 3, 4 | controlled search, filter slot, debounce callback |
ResourcePagination |
2~7 | PageData 기반 page/size control |
ResponsiveResourceList |
2~6 | desktop/mobile rendering slot |
ConfirmDeactivateDialog |
2~5 | 대상명·영향·확인 callback |
UnsavedChangesGuard |
2~7 | dirty form route 이탈 확인 |
FileField, ImageCropDialog |
2~5 | file input과 주입된 crop policy 실행 |
UploadProgress |
3, 5 | 진행률·취소·재시도 callback |
AdminAudioPlayer, AudioPlaybackProvider |
3, 5 | native audio control과 단일 재생 상태 |
이 표는 PRD에 이미 확정된 반복 소비만 포함한다. PageHeader는 실제 구현·소비가 없고 현재 필요한 반복 소비 컴포넌트가 아니므로 만들지 않는다. 각 도메인 Phase에서 필요하다고 확인될 때 추가한다.
Task 1.1 최소 디자인 시스템
Goal 실행 P1-T1: light-only token과 실제 소비되는 최소 shadcn primitive를 접근성 기준으로 고정한다.
-
시작 조건:
P0-GATE완료, PRD10.1~10.9확인. -
완료 증거: 체크박스 전체, token/component test, UX 검색 채택·제외 기록, typecheck·lint·build.
-
범위 밖: dark mode, 전체 primitive 선행 scaffold, 도메인 전용 UI.
-
PRD
10.9의ui-ux-pro-maxdesign-system 검색을 실행하고 관리자 제품에 맞는 채택·제외 결과를 작업 기록에 남긴다. -
Tailwind CSS와 shadcn/ui CSS variable mode를 구성한다.
-
brand-500=#00BDF7, hover#00A9DE, active#009DCE, primary foreground#062B36, link/ring#007EA8을 primitive → semantic → component token으로 연결한다. -
핵심 foreground 대비, control boundary 3:1, primary 위 흰색 금지를 token test로 고정한다.
-
밝은
:roottoken만 만들고.dark, ThemeProvider, theme toggle, system dark 연동이 없음을 test한다. -
Korean system font stack, mobile input 16px, 44px target, focus ring, reduced motion, semantic z-index를 base style에 둔다.
-
상태 Badge가 text label을 포함하고 색상만으로 상태를 전달하지 않는 test를 작성한다.
-
icon-only action에는 accessible name과 Tooltip이 있고 필수 control boundary·focus indicator가 인접 배경 대비 3:1 이상인지 test한다.
-
Auth와 공통 상태에 실제 필요한 shadcn component만 추가한다. 전체 component를 선행 scaffold하지 않는다.
Task 1.2 공통 API·server state
Goal 실행 P1-T2: 인증 저장 adapter와 API envelope·오류·pagination·query 정책을 독립 contract로 완성한다.
-
시작 조건:
P1-T1완료, PRD/API Contract 공통 규칙 확인. -
완료 증거: 체크박스 전체, API/session/pagination/query test와 typecheck·lint·build 기록.
-
범위 밖: 로그인 화면, 보호 route, 도메인 endpoint adapter.
-
AuthSessionRecord = { token: string; role: "ADMIN" }의 읽기·저장·제거 adapter를 먼저 만들고, API client는 React provider가 아니라 이 adapter의 token reader/clear callback에만 의존한다. -
ApiResponse<T>성공형이errorProperty생략과null을 모두 수용하고 오류형은 비2xx status·message·errorProperty를 보존하는 test를 작성한다. -
PageData<T>와page=0,size=20, size 최소 20·최대 50 보정을 test한다. 문서에 없는 음수 page 동작은 추정하지 않는다. -
모든 요청에
Accept-Language: ko를 붙이고 로그인 요청에는 Authorization을 제외하는 test를 작성한다. -
보호 요청과 logout에만 현재 session의 Bearer token을 붙이는 test를 작성한다.
-
400/404/405/415/500의 서버 한국어 message를 공통
ApiError가 보존하는 MSW test를 작성한다. -
동시에 여러 401이 와도 session clear·알림·login redirect가 한 번만 발생하는 test를 작성한다.
-
403은 session을 지우지 않고 AccessDenied 상태로 전달하는 test를 작성한다.
-
logger가 JWT, password, signed URL, multipart body를 받지 않는 test를 작성한다.
-
TanStack Query provider와 공통 retry 정책을 구성하되 401/403과 mutation을 무조건 재시도하지 않는다.
Task 1.3 로그인·session·logout
Goal 실행 P1-T3: ADMIN login, session 복원과 실패해도 local session을 제거하는 logout 흐름을 완성한다.
-
시작 조건:
P1-T2완료. -
완료 증거: 체크박스 전체, auth API/session/LoginPage test와 민감정보 저장 금지 검증 기록.
-
범위 밖: 보호 route와 Admin shell 조합.
-
email 형식, password 필수, visible label, 오류 연결, first-invalid-focus test를 작성한다.
-
login이
POST /admin/member/login에{ email, password }JSON만 보내는 contract test를 작성한다. -
응답의
data.token과data.role="ADMIN"만 유효 session으로 인정한다. -
성공 session을
sessionStorage에만 저장하고 같은 탭 새로고침에서 복원하는 test를 작성한다. -
token 누락, role 누락·비ADMIN이면 보호 route를 렌더링하지 않고 저장도 하지 않는 test를 작성한다.
-
localStorage, IndexedDB, cookie에 인증 정보가 기록되지 않는 test를 작성한다. -
refresh endpoint 호출이 0건임을 확인한다.
-
logout이 Bearer header와 body 없이
POST /member/logout을 한 번 호출하는 test를 작성한다. -
logout 성공·비2xx·network error 모두 local session을 제거하고
/login으로 이동하며, 실패 때만 서버 확인 실패 경고를 표시하고 session을 복원하지 않는 test를 작성한다.
Task 1.4 보호 route·Admin shell
Goal 실행 P1-T4: 인증·권한 route 처리와 반응형·접근 가능한 Admin shell을 완성한다.
-
시작 조건:
P1-T3완료. -
완료 증거: 체크박스 전체, route/shell test, auth·accessibility E2E와 viewport/axe 기록.
-
범위 밖: Phase 2 Character 데이터와 mutation.
-
미인증 사용자가 보호 content를 한 프레임도 보지 않고
/login으로 이동하는 test를 작성한다. -
401은 session 제거 후 login, 403은 AccessDeniedPage로 가는 route test를 작성한다.
-
desktop sidebar, mobile Sheet, header, logout, breadcrumb, skip link,
mainlandmark를 구현한다. -
keyboard로 login, navigation, logout을 완료하고 dialog/menu focus가 trigger로 복귀하는 test를 작성한다.
-
/ai-characters에는 Phase 2가 교체할 명시적 빈 route state만 두고 가짜 도메인 데이터를 만들지 않는다. -
320px과 200% zoom에서 shell overflow와 가려진 control이 없는지 E2E로 확인한다.
-
shell route의 axe critical·serious 위반 0건을 확인한다.
Task 1.5 공통 화면·form component
Goal 실행 P1-T5: 반복 소비가 확정된 상태·검색·목록·확인·이탈 방지 UI contract를 완성한다.
-
시작 조건:
P1-T1,P1-T2, 소비처 Matrix 확인. -
완료 증거: 체크박스 전체, component 독립 test와 현재 Admin shell에서 실제 소비되는
PageStateintegration test 기록. -
범위 밖: domain DTO/endpoint 결합과 단일 화면 전용 wrapper.
-
위 소비처 Matrix를 contract test와 component API에 대조하고 모든 shared component가 두 Phase 이상에서 같은 의미로 사용되는지 확인한다.
-
PageState가 loading·empty·error·retry를 접근 가능한 status/alert와 keyboard action으로 표현하는 test를 작성한다. -
StatusBadge가 domain label·icon/보조 문구를 slot으로 받고 색상만으로 상태를 전달하지 않는 test를 작성한다. -
SearchToolbar는 controlled search/filter slot과 약 300ms debounce·URL query callback만 제공하고 특정 endpoint query를 알지 않게 한다. -
ResourcePagination은 공통PageData로 page/size를 제어하고 disabled·accessible name·keyboard 동작을 제공한다. -
ResponsiveResourceList는 desktop/mobile rendering slot만 제공하고 domain column·DTO·action을 prop union으로 내장하지 않는다. -
ConfirmDeactivateDialog는 대상명·영향 설명·확인 callback을 조합하고 Switch로 대체되지 않게 test한다. -
UnsavedChangesGuard는 dirty 상태에서만 route 이탈을 확인하고 저장 성공 후 해제되며 focus를 trigger로 복귀한다. -
UTC 시각의 Asia/Seoul 표시와 0 이상 정수 “캔” 표시를 공통 formatter로 고정하고 domain status label은 formatter에 넣지 않는다.
-
각 component는 독립 RTL test를 먼저 통과시킨다. 현재 Admin shell은 실제 소비되는
PageState만 조합하고, 나머지 반복 소비 component는 각 도메인 Phase에서 처음 쓰일 때 integration test를 작성한다.
Task 1.6 공통 file·media component
Goal 실행 P1-T6: domain-neutral file validation, crop, upload 상태와 단일 audio 재생 contract를 완성한다.
-
시작 조건:
P1-T1,P1-T2완료, 파일 경계 계약 확인. -
완료 증거: 체크박스 전체, validation/crop/file/upload/player test와 dependency-boundary 기록.
-
범위 밖: 도메인별 multipart request와 resource form.
-
image
10MB의 정확한 byte 기준을10,485,760 bytes로 확정하고 PRD·API Contract·구현 상수·경계 test에 기록한다. -
FileField는 visible label, 설명·오류 연결, accept 안내, keyboard activation, 선택 취소와 controlledFile | nullcontract만 제공한다. -
공통 file validation은 주입된 allowed extension·MIME·maxBytes를 함께 확인한다. JPEG/PNG·GIF 같은 resource별 allowed set은 각 도메인 policy가 소유한다.
-
공통 audio policy는 MP3/AAC/M4A,
.m4a + audio/x-m4a,1,024,000,000 bytes경계, WAV 거부를 표현하되 실제 container·codec을 client에서 판정하지 않는다. -
ImageCropDialog는 주입된 aspect/max width 정책으로 이동·zoom·reset·preview·취소·적용·keyboard/button 대안·no-upscale 결과를 제공한다. -
crop interaction은 검증된 단일 dependency가 native pointer/Canvas 직접 구현보다 코드·접근성 위험을 줄이는지 확인해 하나만 선택하고, Canvas는 결과 File 생성에만 사용한다.
package.json에 crop dependency가 없어 새 dependency 없이 button/range/keyboard 이동을 선택했고 Canvas는 결과 File 생성에만 사용한다. -
공통 image policy는
aspect,maxWidth,noUpscale, crop 적용 여부를 받는 domain-neutral contract만 정의한다. Character·Audio·Series·Community profile과 GIF 예외는 각 feature Phase가 소유한다. -
UploadProgress는 진행률·취소·재시도 callback과 상태 표시만 담당하고 Axios request나 domain form을 직접 소유하지 않는다. -
AdminAudioPlayer는 native audio를 감싸 play/pause·seek·time·volume·speed·keyboard·일반 오류·수동 재시도를 제공하고 download와 자동 refetch/자동 play를 만들지 않는다. -
AudioPlaybackProvider가 동시에 하나의 player만 재생되게 하며 signed URL을 log·storage에 전달하지 않는 test를 작성한다. -
공통 file/media component는 endpoint·query cache·domain DTO를 import하지 않는 dependency test 또는 review checklist를 통과한다.
Phase 1 Gate
Goal 실행 P1-GATE: 공통 component와 ADMIN 인증 journey가 후속 Phase 선행조건을 충족하는지 판정한다.
- 시작 조건:
P1-T1~P1-T6완료. - 완료 증거: 아래 명령과 Expected 전체 통과 및 Phase 1 종합 검증 기록.
- 범위 밖: 실패와 무관한 도메인 구현. 실패는 소유 Task 수정 goal로 되돌린다.
npm run test:run -- src/shared src/features/auth src/layouts src/app/App.test.tsx
npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts
npm run typecheck
npm run lint
npm run build
Expected: 공통 component contract가 독립 test로 고정되고, ADMIN login → 보호 shell → 새로고침 session 복원 → logout이 동작하며 비ADMIN·stale claim·401·logout 실패 경로가 독립적으로 검증된다.
Task R1.2 — image 최대 용량 exact byte 계약 확정
Goal 실행 P1-R2: image 10MB 표현을 백엔드와 동일한 exact byte 계약으로 확정한다.
- 연결 리뷰:
REV-P1-010 - 상태: 완료.
- 결정: 백엔드 exact byte를
10,485,760 bytes로 확정했다.10,485,760 bytes는 허용하고10,485,761 bytes부터 거부한다. - 완료 증거: PRD, API 계약,
IMAGE_MAX_BYTES,limit-1/limit/limit+1경계 테스트를 같은 값으로 정렬했다.
Task R1.3 — 인증 보호·오류·logout 회귀 수정
Goal 실행 P1-R3: Phase 1 인증의 보호 경계, 오류 피드백, logout 단일 요청 계약을 복구한다.
-
연결 리뷰:
REV-P1-002,REV-P1-006,REV-P1-007 -
완료 증거: stale ADMIN probe pending·403 동안 보호 shell 비노출, probe
size=20, 서버 한국어 message 우선 표시, logout in-flight 중 API 1회 호출. -
범위 밖: 인증 방식 또는 token 저장 방식 교체, Phase 2 권한 기능.
-
지연 403 전 보호 shell 비노출과 probe
size=20request assertion을src/app/App.test.tsx에 추가했다. -
로그인 실패 시
Error.message의 서버 한국어 message를 우선 표시하도록LoginPage를 수정했다. -
AuthSessionProvider.logout에 in-flight promise coalescing을 추가해 연속 입력도/member/logout1회만 보내게 했다.
Task R1.4 — crop·음성 파일 정책 회귀 수정
Goal 실행 P1-R4: Phase 1 파일·미디어 입력의 crop 결과와 audio 형식 조합 계약을 복구한다.
-
연결 리뷰:
REV-P1-003,REV-P1-004 -
완료 증거: source crop rectangle 기반 출력, 1200×600 → 1:1 no-upscale 600×600과 상·하단 alpha 유지 test, 자유 비율, pointer drag, 확장자↔MIME 조합 table test.
-
범위 밖: 외부 crop library, 서버 파일 변환 구현.
-
calculateCropOutputSize가 source height와 crop rectangle을 반영하도록 수정했다. -
createCroppedImageFile이 원본 전체 draw 대신 source rectangle을drawImage에 전달하도록 수정했다. -
createCroppedImageFile의 1200×600 → 1:1 no-upscale 600×600 출력과 상·하단 alpha 유지 회귀 test를 추가했다. -
ImageCropDialog에aspect: "free"와 pointer drag 이동을 추가했다. -
audio 확장자별 허용 MIME table을 적용하고
mp3+audio/mp4,aac+audio/mpeg,m4a+audio/aac거부 test를 추가했다.
Task R1.5 — 공유 UI keyboard·focus 접근성 회귀 수정
Goal 실행 P1-R5: Phase 1 공유 UI의 modal focus, 필드 오류 상태, keyboard event 경계를 복구한다.
-
연결 리뷰:
REV-P1-005,REV-P1-008,REV-P1-009 -
완료 증거: 세 modal의 focus trap과 trigger focus return, FileField
aria-invalid, audio descendant control Enter·Space 격리. -
범위 밖: 공유 UI 전체 교체, 시각 디자인 재작업.
-
useModalFocus를 추가해ConfirmDeactivateDialog,UnsavedChangesGuard,ImageCropDialog의 초기 focus, Tab loop, 닫힌 뒤 trigger focus return을 공유했다. -
FileFielderror 상태 input에aria-invalid="true"를 연결했다. -
AdminAudioPlayergroup shortcut은 event target이 group 자체일 때만 처리하도록 막았다.
P1-R3/R4/R5 수정 검증 기록 (2026-07-27):
- RED:
npm run test:run -- src/app/App.test.tsx src/features/auth/tests/login-page.test.tsx src/features/auth/tests/auth-session.test.tsx src/shared/lib/crop-image.test.ts src/shared/validation/file-media-policy.test.ts src/shared/ui/__tests__/file-field.test.tsx src/shared/ui/__tests__/admin-audio-player.test.tsx src/shared/ui/__tests__/confirm-deactivate-dialog.test.tsx src/shared/ui/__tests__/unsaved-changes-guard.test.tsx src/shared/ui/__tests__/image-crop-dialog.test.tsx src/shared/test/setup-isolation.test.ts— 기대 실패 확인, 11 files failed / 13 failed / 40 passed. - GREEN focused: 같은 command — 성공, 11 files / 55 tests passed.
- Auth focused:
npm run test:run -- src/app/App.test.tsx src/features/auth/tests/auth-session.test.tsx— 성공, 2 files / 23 tests passed. - P1-GATE unit:
npm run test:run -- src/shared src/features/auth src/layouts src/app/App.test.tsx— 성공, 27 files / 102 tests passed. - P1-GATE E2E:
npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts— 성공, 4 projects / 16 tests passed. 320×640 viewport, CSS zoom 2, keyboard-contained mobile menu, axe critical·serious 0건을 포함한다. - 전체 unit:
npm run test:run— 성공, 28 files / 108 tests passed. npm run typecheck,npm run lint,npm run build— 모두 성공.git diff --check— 성공.
문서·체크 정정 검증 기록 (2026-07-27):
docs/agent-guide/README.md참조는 실제 존재하는docs/agent-guide/세부 문서 관리 방식으로 정정했다.P1-R4의 pixel·alpha 실행 체크는src/shared/lib/crop-image.test.ts의createCroppedImageFile keeps a wide no-upscale crop opaque at the top and bottomtest로 보강했다.P1-R2의 image 10MB exact byte는 백엔드 확인값10,485,760 bytes로 PRD·API 계약·Task 1.6 체크·구현 상수·경계 테스트를 정렬했다.- Fresh focused:
npm run test:run -- src/shared/lib/crop-image.test.ts— 성공, 1 file / 4 tests passed. - Fresh P1-GATE unit:
npm run test:run -- src/shared src/features/auth src/layouts src/app/App.test.tsx— 성공, 27 files / 102 tests passed. - Fresh 전체 unit:
npm run test:run— 성공, 28 files / 108 tests passed. - Fresh E2E:
npm run e2e -- tests/e2e/smoke.spec.ts— 성공, 4 tests passed.npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts— 성공, 16 tests passed.
P1-R1/R2 closure 점검 기록 (2026-07-27):
P1-R2: image exact byte를10,485,760 bytes로 확정하고 PRD, API 계약,IMAGE_MAX_BYTES,limit-1/limit/limit+1경계 테스트를 정렬했다.PageHeader: 구현·소비가 없고 현재 필요한 반복 소비 컴포넌트가 아니므로 Phase 1 shared 산출물과 소비처 Matrix에서 제거했다. 필요하면 해당 도메인 Phase에서 새로 추가한다.- RED:
npm run test:run -- src/shared/validation/file-media-policy.test.ts—policy.maxBytes미구현으로 실패 확인. 이후IMAGE_MAX_BYTESexport 누락으로 실패 확인. - Focused:
npm run test:run -- src/shared/validation/file-media-policy.test.ts src/app/App.test.tsx src/shared/ui/__tests__/page-state.test.tsx— 성공, 3 files / 21 tests passed. - P1-GATE unit:
npm run test:run -- src/shared src/features/auth src/app/App.test.tsx— 성공, 27 files / 103 tests passed. - P1-GATE E2E:
npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts— 성공, 16 tests passed. 320px·200% zoom, keyboard-contained mobile menu, axe critical·serious 0건을 포함한다. - 전체 unit:
npm run test:run— 성공, 28 files / 109 tests passed. - Smoke E2E:
npm run e2e -- tests/e2e/smoke.spec.ts— 성공, 4 tests passed. npm run typecheck,npm run lint,npm run build,git diff --check— 모두 성공.- LSP diagnostics:
src/shared/validation/file-media-policy.test.ts오류 0건.src/shared/validation/image-policy.ts개별 fresh diagnostics는 timeout이 있었고,npm run typecheck와npm run build성공으로 보완 확인했다.
Task R1.1 — Phase 1 산출물·통합·Gate 완료 증거 복구
Goal 실행 P1-R1: Phase 1 완료 판정을 재현 가능한 산출물·통합·Gate 증거로 복구한다.
- 연결 리뷰:
REV-P1-001 - 상태: 완료.
- 완료한 부분: P1-GATE 명령은
src/app/App.test.tsx를 포함하도록 보정했고, P1-R2/R3/R4/R5의 자동·E2E 검증 기록을 누적했다. - 결정:
PageHeader는 구현·소비가 없고 현재 필요한 반복 소비 컴포넌트가 아니므로 Phase 1 shared 산출물에서 제거한다. 필요하다고 판단되는 도메인 Phase에서 추가한다. - 완료 증거: P1-R2 exact byte와 PageHeader 폐기 결정을 반영했고, closure Gate를 재실행해 열린 Blocker·High·Medium 0건으로 정리했다.
Phase 2. Character workspace vertical slice
목표: ADMIN이 active Character를 검색·생성·선택하고 workspace에서 상세·수정·soft delete까지 완료한다.
Phase Goal P2: Task 2.1 → 2.4와 Phase 2 Gate로 Character workspace vertical slice를 완성한다.
- 시작 조건:
P1-GATE완료. - 완료 조건:
P2-T1~P2-T4,P2-GATE완료. 외부 의존은 구현 또는 명시적 제외 결정으로 종결. - 실행 순서: 계약 확인 → 목록/workspace → mutation → 반응형·접근성.
요구사항: CHAR-001~014, FILE-001~002, FILE-008~010, FILE-012, PRD 7, 9의 Character 범위.
외부 의존: CHAR-013 original work lookup·미선택 직렬화, 신규 Character 오류 계약.
주요 Files:
- Create:
src/features/characters/api/character-api.ts - Create:
src/features/characters/model/types.ts - Create:
src/features/characters/schemas/character-schema.ts - Create:
src/features/characters/validation/character-image-policy.ts - Create:
src/features/characters/pages/{CharacterListPage,CharacterDetailPage,CharacterFormPage}.tsx - Create:
src/features/characters/components/{CharacterList,CharacterListItem,CharacterProfile,CharacterForm,CharacterImageField}.tsx - Create when
CHAR-013contract is available:src/features/characters/components/OriginalWorkCombobox.tsx - Create:
src/features/characters/tests/character-api.test.ts - Create:
src/features/characters/tests/{character-list,character-form}.test.tsx - Create:
src/layouts/CharacterWorkspaceLayout.tsx - Create:
src/layouts/CharacterWorkspaceLayout.test.tsx - Create:
tests/e2e/character-workspace.spec.ts - Modify:
src/app/router.tsx,src/app/route-paths.ts
Task 2.1 Phase 계약 확인
Goal 실행 P2-T1: Character 구현 계약과 화면 component map을 확정한다.
-
시작 조건:
P1-GATE완료, PRDCHAR-001~014와 API Contract §4 확인. -
완료 증거: 체크박스 전체, 제공 계약 또는 제외 결정의 세 문서 일치, 상태/action inventory.
-
범위 밖: 계약을 추정한 production adapter와 Character 화면 구현.
-
original work lookup endpoint, DTO, search/page, 미선택
null/omit canonical form을 확인한다. -
계약이 없으면 original work network control과 serializer를 추측하지 않고, 현재 slice에서 제외할 범위를 PRD·API Contract·plan에 먼저 기록한다.
-
Character 도메인 오류의 비2xx status와 message key를 contract fixture에 기록한다.
-
목록·상세·form·workspace의 상태/action inventory를 작성하고 Page는 route/query/policy 조합, feature component는 Character 표시·입력 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.
Task 2.2 목록·선택·workspace
Goal 실행 P2-T2: active Character 목록·검색·선택과 URL 기반 workspace 복원을 완성한다.
-
시작 조건:
P2-T1완료. -
완료 증거: 체크박스 전체, route/list/workspace test와 read-only/error 상태 검증 기록.
-
범위 밖: Character 생성·수정·비활성화 form.
-
Character 목록·생성 path에는
characterId가 없고 하위 resource route에만 선택한characterId가 들어가는 contract test를 작성한다. -
active-only 목록의
search,page,sizeURL query 보존과 loading·empty·error·retry test를 작성한다. -
목록 request에
isActive·activeStatus가 없고 client-side 활성 filter도 없는 contract test를 작성한다. -
Character 선택 시 URL의
characterId로 workspace에 진입하고 새로고침·deep link가 동작하는 test를 작성한다. -
workspace header에 image, name, active 상태,
characterId와 탭·breadcrumb를 표시한다. -
상세 성공 응답이
isActive=false이면 read-only 배너와 중앙 write policy로 모든 mutation 진입점을 차단한다. -
상세 400/404/500은 공통 오류 화면을 사용하고 비활성 ID 응답 정책을 client가 추정하지 않는다.
Task 2.3 생성·수정·soft delete
Goal 실행 P2-T3: Character multipart 생성·수정·soft delete와 image/original-work 규칙을 완성한다.
-
시작 조건:
P2-T1,P2-T2완료. -
완료 증거: 체크박스 전체, serializer/form/image/deactivate test,
OQ-009결정과 검증 기록. -
범위 밖: 계약 미제공 original-work integration과 하위 도메인 mutation.
-
create multipart가
requestJSON part와 optional image만 보내며isActive,externalCharacterId를 포함하지 않는 test를 작성한다. -
일반 update는
isActive를 생략하고 soft delete만isActive=false를 보내며true를 보내지 않는 test를 작성한다. -
name·description visible label, field error, 중복 제출 방지, dirty-form 이탈 확인을 test한다.
-
Character image의 JPEG/PNG·10MB,
1:1, 최대 800×800, no-upscale, crop 이동·zoom·reset·preview·취소·적용·keyboard 대안을 test한다. -
crop 취소·교체 파일 미선택이 기존 image를 유지하고 기존 image 제거 UI는 없음을 test한다.
-
original work 계약이 제공됐다면 이름 검색 Combobox와 canonical 미선택 payload를 contract test로 고정한다.
-
creator member ID·nickname 등 응답 정보는 read-only로 표시하고 creator 생성·동기화를 client가 수행하지 않는다.
-
저장 성공 후 server response로 list/detail cache를 갱신한다.
-
비활성화 AlertDialog가 영향·복원 미지원·hard delete 미지원을 설명하는 test를 작성한다.
-
soft delete 성공 후 active-only 목록 재조회, 목록 이동, 성공 toast를 확인하고 상세에 머물지 않는다.
-
Character form을 실제로 작성한 뒤
OQ-009의name·description상한 필요성을 판단하고, 구현 전 결정 문서를 갱신하거나 “상한 추가 없음”으로 종결한다.
Task 2.4 Character 반응형·접근성
Goal 실행 P2-T4: Character viewport capability와 keyboard·zoom·axe 기준을 검증한다.
-
시작 조건:
P2-T2,P2-T3의 활성 범위 완료. -
완료 증거: 체크박스 전체, 320/768/1280px·200% zoom·keyboard·axe E2E 기록.
-
범위 밖: 다음 Phase 도메인과 모바일 mutation.
-
desktop/tablet에서는 전체 관리 action을 제공한다.
-
mobile에서는 목록·검색·상세만 제공하고 create/edit/deactivate route 직접 진입도 desktop 안내로 종료한다.
-
Table이 mobile Card로 바뀌어도 동일한 accessible name과 핵심 상태를 유지한다.
-
keyboard-only로 search → select → tabs → form → dialog를 완료한다.
-
320/768/1280px, 200% zoom, axe critical·serious 0건을 Phase E2E에서 확인한다.
Phase 2 Gate
Goal 실행 P2-GATE: Character 전체 journey와 공통 품질 Gate를 최종 판정한다.
- 시작 조건:
P2-T1~P2-T4완료. - 완료 증거: 아래 명령과 Expected 통과, 외부 의존의 구현/제외 상태 기록.
- 범위 밖: 실패와 무관한 Audio 기능.
npm run test:run -- src/features/characters src/layouts/CharacterWorkspaceLayout.test.tsx
npm run e2e -- tests/e2e/character-workspace.spec.ts
npm run typecheck
npm run lint
npm run build
Expected: login → Character 검색/생성 → 선택/workspace → 수정 → soft delete → active-only 목록 복귀가 한 slice로 통과한다. original work 계약이 없으면 그 기능의 제외 결정과 문서가 명시돼야 하며 완료로 가장하지 않는다.
Phase 3. Audio vertical slice
목표: 선택 Character의 Audio를 검색·검수·발행·수정·비활성화하고 대용량 upload를 안전하게 제어한다.
Phase Goal P3: Task 3.1 → 3.4와 Phase 3 Gate로 Audio 조회·재생·발행·upload slice를 완성한다.
- 시작 조건: Phase 2 workspace core(
P2-T2)와P1-T6완료. - 완료 조건:
P3-T1~P3-T4,P3-GATE완료. 오류·price 계약은 제공값 또는 명시된 최소 규칙으로 종결. - 실행 순서: 계약 확인 → 목록/상세/player → form/upload → 반응형·접근성.
요구사항: AUDIO-001~028, FILE-001~002, FILE-006~009, FILE-012~013, PRD 9의 Audio 범위.
외부 의존: Audio 도메인 오류 계약, optional P1 price 상한. price 상한이 없으면 0 이상 정수만 적용한다.
주요 Files:
- Create:
src/features/audio-contents/api/{audio-content-api,audio-content-theme-api,series-options-api,upload-audio-content}.ts - Create:
src/features/audio-contents/model/types.ts - Create:
src/features/audio-contents/schemas/audio-content-schema.ts - Create:
src/features/audio-contents/validation/audio-cover-policy.ts - Create:
src/features/audio-contents/pages/{AudioContentListPage,AudioContentDetailPage,AudioContentFormPage}.tsx - Create:
src/features/audio-contents/components/{AudioContentList,AudioContentListItem,AudioContentSummary,AudioContentForm,AudioContentThemeSelect,ReleaseScheduleField,SeriesMultiCombobox}.tsx - Create:
src/features/audio-contents/tests/{audio-contract,audio-upload}.test.ts - Create:
src/features/audio-contents/tests/{audio-list,audio-player,audio-form}.test.tsx - Create:
tests/e2e/audio-content.spec.ts
Task 3.1 Phase 계약 확인
Goal 실행 P3-T1: Audio 오류·price·theme·상태 계약과 component map을 확정한다.
-
시작 조건:
P2-T2,P1-T6완료, PRDAUDIO-001~028와 API Contract §5 확인. -
완료 증거: 체크박스 전체, contract fixture와 상태/action inventory의 세 문서 일치.
-
범위 밖: 오류 status/key 또는 price 상한 추정과 Audio UI 구현.
-
Audio 신규 오류 status/message key와 backend container·codec 오류 fixture를 기록한다.
-
오디오 테마 목록
GET /api/v2/admin/ai-characters/audio-content-themes가 query/body 없이 호출되고themeId,themeName,imageUrl배열을 반환하는 contract fixture를 기록한다. -
price 최대값이 제공되면 schema와 경계 test를 추가하고, 없으면 상한을 만들지 않는다.
-
status query 미전송 시 server가 결과 집합을 결정한다는 계약을 유지하고 client fixture에서 임의 집합을 강제하지 않는다.
-
목록·상세·player·form/upload의 상태/action inventory를 작성하고 Page는 route/query/policy 조합, feature component는 Audio 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.
Task 3.2 목록·상세·player
Goal 실행 P3-T2: Audio active-only 목록·상세 복원과 안전한 단일 재생 흐름을 완성한다.
-
시작 조건:
P3-T1완료. -
완료 증거: 체크박스 전체, list/detail/player test와 signed URL 비기록·no-auto-refetch 기록.
-
범위 밖: 생성·수정·upload form.
-
status type과 filter가
OPEN | SCHEDULED만 허용하고 서버 값을 client가 재계산하지 않는 test를 작성한다. -
검색·status·page URL 보존, active-only request, loading·empty·error·retry를 test한다.
-
Audio detail route의 직접 진입과 새로고침에서 같은 resource를 복원하는 test를 작성한다.
-
목록과 상세가 Phase 1
AdminAudioPlayer를 조합하고 play/pause, seek, current/duration, volume, speed, keyboard를 지원하는 integration test를 작성한다. -
한 player 재생 시 기존 player가 정지되고 명시적 download button이 없음을 test한다.
-
media error를 signed URL 만료로 추정하지 않고 일반 오류·수동 재시도·페이지 새로고침 안내를 표시한다.
-
media error만으로 목록/detail GET과
play()가 자동 재호출되지 않는 test를 작성한다. -
signed URL이 log·storage·분석 event로 전달되지 않는 test를 작성한다.
Task 3.3 발행 form·upload
Goal 실행 P3-T3: theme 선택, 즉시/예약 발행, multipart media와 진행률·취소·재시도를 완성한다.
-
시작 조건:
P3-T1,P3-T2완료. -
완료 증거: 체크박스 전체, contract/form/upload/file-boundary test,
OQ-009결정과 검증 기록. -
범위 밖: resumable upload, client codec 판정, 계약 없는 price 상한.
-
생성은 cover image와 audio file 필수, 수정 교체 파일은 optional이며 미전송 시 기존 media 유지임을 test한다.
-
Audio cover가 Phase 1
FileField·ImageCropDialog의 JPEG/PNG·10MB,1:1, 최대 800px, no-upscale profile을 조합하는 test를 작성한다. -
MP3/AAC/M4A 허용, WAV 거부, extension/MIME 조합을 test한다.
-
.m4a + audio/x-m4a만 호환 조합으로 허용하고 실제 container·codec 판정은 server 책임으로 둔다. -
1,024,000,000 bytes허용,1,024,000,001 bytes거부 경계 test를 작성한다. -
price는 0 이상 정수 “캔”으로 입력·format한다.
-
생성 form은 오디오 테마 목록을 불러와 visible label이 있는 선택 UI를 제공하고, 미선택 제출을 차단하며 선택한
themeId를 create payload에 포함하는 test를 작성한다. -
즉시 공개 기본값은 날짜 입력을 비활성화·초기화하고
releaseDateUtc=null을 보낸다. -
예약 공개는 미래 Asia/Seoul 시각만 받고 UTC ISO-8601
Z로 변환하는 test를 작성한다. -
수정 form은 server
releaseDateUtc/status로 초기화하고 사용자가 바꾸지 않으면 기존 값을 유지한다. -
제공된 active Series 목록 endpoint를 사용하는 options request와
seriesIds다중 선택·keyboard 제거를 test한다. -
create payload에 필수
themeId가 있고status,isActive가 없으며 update/soft delete의isActive규칙이 지켜지는 contract test를 작성한다. -
upload 진행률, AbortController 취소, 전체 재시도, 실패 후 form/file 상태 보존을 test한다.
-
415 server 오류를 field 안내로 보존하고 resumable upload는 만들지 않는다.
-
저장 성공은 detail/list cache를 갱신하고 soft delete 성공은 active-only 목록 이동과 toast로 끝낸다.
-
Audio form을 실제로 작성한 뒤
OQ-009의title·description·seriesIds상한 필요성을 판단하고, 구현 전 결정 문서를 갱신하거나 “상한 추가 없음”으로 종결한다.
Task 3.4 Audio 반응형·접근성
Goal 실행 P3-T4: Audio viewport capability와 player/form 접근성 기준을 검증한다.
-
시작 조건:
P3-T2,P3-T3의 활성 범위 완료. -
완료 증거: 체크박스 전체, 320px·keyboard·200% zoom·axe E2E 기록.
-
범위 밖: 모바일 create/edit/deactivate/upload.
-
mobile에서는 목록·상세·player만 제공하고 create/edit/deactivate/upload는 숨김이 아닌 route capability로 차단한다.
-
desktop/tablet에서는 모든 form·upload action을 제공한다.
-
320px에서 player control, error text, 긴 title이 overflow하지 않는 E2E를 작성한다.
-
keyboard-only player/form, 200% zoom, axe critical·serious 0건을 확인한다.
Phase 3 Gate
Goal 실행 P3-GATE: Audio journey, upload 복구와 media 오류 불변식을 최종 판정한다.
- 시작 조건:
P3-T1~P3-T4완료. - 완료 증거: 아래 명령과 Expected 통과 및 Phase 검증 기록.
- 범위 밖: 실패와 무관한 Series/Community 구현.
npm run test:run -- src/features/audio-contents src/shared/ui src/shared/validation
npm run e2e -- tests/e2e/audio-content.spec.ts
npm run typecheck
npm run lint
npm run build
Expected: Audio 즉시/예약 생성 → 진행률/취소/재시도 → 목록·상세 재생 → 수정 → soft delete가 통과하고 media error가 자동 refetch·자동 재생을 0회 발생시킨다.
Phase 4. Series vertical slice
목표: 선택 Character의 Series를 생성·수정·비활성화하고 Audio 연결·해제와 활성 Series 전체 순서를 관리한다.
Phase Goal P4: Task 4.1 → 4.4와 Phase 4 Gate로 Series CRUD·연결·전체 순서 slice를 완성한다.
- 시작 조건:
P3-T2의 Audio 조회 API 완료. - 완료 조건:
P4-T1~P4-T4,P4-GATE완료. 계약 없는 genre·연결·순서는 구현 또는 명시적 제외 결정으로 종결. - 실행 순서: 계약 확인 → CRUD → 연결/순서 → 반응형·접근성.
요구사항: SERIES-001~013, FILE-001~002, FILE-005, FILE-007~009, FILE-012, FILE-015, PRD 9의 Series 범위.
외부 의존: genre lookup(SERIES-011), 연결 후보, 50개 초과 전체 로딩, 누락 ID, 동시 충돌, 신규 오류 계약.
주요 Files:
- Create:
src/features/series/api/series-api.ts - Create:
src/features/series/model/types.ts - Create:
src/features/series/schemas/series-schema.ts - Create:
src/features/series/validation/series-image-policy.ts - Create:
src/features/series/pages/{SeriesListPage,SeriesDetailPage,SeriesFormPage,SeriesOrderPage}.tsx - Create:
src/features/series/components/{SeriesList,SeriesListItem,SeriesSummary,SeriesForm,PublishedDaysField,GenreCombobox,SeriesContents,SeriesOrderList}.tsx - Create:
src/features/series/tests/series-contract.test.ts - Create:
src/features/series/tests/{series-form,series-contents,series-order}.test.tsx - Create:
tests/e2e/series.spec.ts
Task 4.1 Phase 계약 확인
Goal 실행 P4-T1: Series genre·연결 후보·전체 순서·오류 계약과 component map을 확정한다.
-
시작 조건:
P3-T2완료, PRDSERIES-001~013와 API Contract §6 확인. -
완료 증거: 체크박스 전체, 제공 계약 또는 제외 결정의 세 문서 일치, 상태/action inventory.
-
범위 밖: 계약 없는 genre/연결/순서 network 구현.
-
genre lookup endpoint·DTO·search/page 계약을 기록한다.
-
선택 Character의 연결 가능한 active Audio 후보 계약을 기록한다.
-
활성 Series가 50개를 넘을 때 전체를 누락 없이 읽는 방식과 누락 ID·동시 변경 충돌 오류를 기록한다.
-
계약이 없는 연결·순서·genre 기능은 추측 구현하지 않고 제외/후속 여부를 문서에서 먼저 결정한다.
-
목록·상세·form·연결·순서 화면의 상태/action inventory를 작성하고 Page는 route/query/policy 조합, feature component는 Series 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.
Task 4.2 Series CRUD
Goal 실행 P4-T2: Series CRUD, enum·요일·image와 soft delete 규칙을 완성한다.
-
시작 조건:
P4-T1완료. -
완료 증거: 체크박스 전체, contract/form/image/list/detail/deactivate test와 검증 기록.
-
범위 밖: Audio 연결·해제와 전체 순서 저장.
-
list가 active-only이며 활성 query를 보내지 않고 loading·empty·error·retry를 제공하는 test를 작성한다.
-
Series detail route의 직접 진입과 새로고침에서 같은 resource를 복원하는 test를 작성한다.
-
enum은
PROCEEDING | SUSPEND | COMPLETE, 요일은SUN~SAT | RANDOM만 허용한다. -
생성 form에 state 입력이 없고 payload에도
state,isActive가 없음을 test한다. -
수정에서 state 미선택은 key 생략, 선택은 유효 enum만 전송하고
null은 보내지 않는다. -
RANDOM은 단독, 실제 요일은 하나 이상이어야 하는 schema·UI test를 작성한다. -
genre 계약이 제공됐다면 이름 검색 후
genreId만 전송하는 Combobox를 test한다. -
Series image JPEG/PNG·10MB,
210:297,height=round(width×297÷210), 최대 1000×1414, 1px 오차, no-upscale을 test한다. -
일반 update와 soft delete의
isActive규칙, soft delete 후 목록 이동·toast를 test한다.
Task 4.3 Audio 연결·해제·전체 순서
Goal 실행 P4-T3: Series Audio 연결·해제와 active Series 전체 순서를 안전하게 관리한다.
-
시작 조건:
P4-T1,P4-T2완료 및 관련 P0 계약 제공. -
완료 증거: 체크박스 전체, link/unlink/reorder contract·interaction test, 충돌 보존,
OQ-009결정 기록. -
범위 밖: 계약 없는 후보/전체 로딩/충돌 동작의 추정 구현.
-
현재 연결 Audio 목록의 search/page와 상세 cache 동기화를 test한다.
-
후보는 선택 Character의 active Audio로 제한하고 이미 연결된 항목을 중복 선택하지 않는다.
-
연결 POST는
{ contentIds }, 해제 DELETE는 body 없음임을 contract test로 고정한다. -
연결 해제 전 대상 title과 영향을 AlertDialog로 확인한다.
-
순서 mode는 active Series 전체를 읽고 최종 순서의 모든
seriesIds를 한 번에 보낸다. -
drag-and-drop과 동일한 결과를 keyboard·위/아래 button으로 만들 수 있는 test를 작성한다.
-
server의 누락 ID·동시 충돌 오류에서 기존 화면 순서를 보존하고 재조회/재시도 안내를 제공한다.
-
Series form·연결·순서 UI를 실제로 작성한 뒤
OQ-009의 title·introduction·keywords·writer·studio·days·contentIds·seriesIds 상한 필요성을 판단하고, 구현 전 결정 문서를 갱신하거나 “상한 추가 없음”으로 종결한다.
Task 4.4 Series 반응형·접근성
Goal 실행 P4-T4: Series viewport capability와 form·연결·정렬 접근성을 검증한다.
-
시작 조건:
P4-T2,P4-T3의 활성 범위 완료. -
완료 증거: 체크박스 전체, 320px·keyboard·200% zoom·axe E2E 기록.
-
범위 밖: 모바일 CRUD·연결·순서 mutation.
-
mobile은 목록·상세·연결 콘텐츠 조회만 제공하고 CRUD·연결·순서 action을 route capability로 차단한다.
-
desktop/tablet에서 전체 관리 흐름을 제공한다.
-
keyboard-only 요일·genre·연결·정렬, 320px 조회, 200% zoom, axe critical·serious 0건을 확인한다.
Phase 4 Gate
Goal 실행 P4-GATE: Series CRUD·연결·전체 reorder journey와 serializer 불변식을 판정한다.
- 시작 조건:
P4-T1~P4-T4완료. - 완료 증거: 아래 명령과 Expected 통과, 외부 의존 상태와 Phase 검증 기록.
- 범위 밖: 실패와 무관한 Community/FanTalk 구현.
npm run test:run -- src/features/series
npm run e2e -- tests/e2e/series.spec.ts
npm run typecheck
npm run lint
npm run build
Expected: Series 생성 → 수정 → Audio 연결/해제 → active 전체 reorder → soft delete가 통과하고 잘못된 enum·부분 순서 payload가 생성되지 않는다.
Phase 5. Community vertical slice
목표: 별도 상세 route/GET 없이 active Community 목록과 Sheet만으로 게시글 등록·조회·수정·고정·비활성화·첨부 재생을 완료한다.
Phase Goal P5: Task 5.1 → 5.4와 Phase 5 Gate로 목록 기반 Community Sheet·media slice를 완성한다.
- 시작 조건:
P1-T6file/media primitive와P3-T2player integration 완료. - 완료 조건:
P5-T1~P5-T4,P5-GATE완료. 오류·price는 제공 계약 또는 최소 규칙으로 종결. - 실행 순서: 계약 확인 → 목록/Sheet → form/media → 반응형·접근성.
요구사항: COMMUNITY-001~011, FILE-001~004, FILE-007~009, FILE-011~014, PRD 9의 Community 범위.
외부 의존: Community 신규 오류 계약, optional P1 price 상한. Comments는 Phase 7에서 연결한다.
주요 Files:
- Create:
src/features/community-posts/api/community-post-api.ts - Create:
src/features/community-posts/model/types.ts - Create:
src/features/community-posts/schemas/community-post-schema.ts - Create:
src/features/community-posts/validation/community-media-policy.ts - Create:
src/features/community-posts/pages/CommunityPostListPage.tsx - Create:
src/features/community-posts/components/{CommunityPostList,CommunityPostListItem,CommunityPostForm,CommunityPostSheet}.tsx - Create:
src/features/community-posts/tests/community-contract.test.ts - Create:
src/features/community-posts/tests/{community-list,community-sheet}.test.tsx - Create:
tests/e2e/community-post.spec.ts
Task 5.1 Phase 계약 확인
Goal 실행 P5-T1: Community 오류·media·price 계약과 목록/Sheet component map을 확정한다.
-
시작 조건:
P1-T6,P3-T2완료, PRDCOMMUNITY-001~011과 API Contract §7 확인. -
완료 증거: 체크박스 전체, contract fixture와 상태/action inventory의 세 문서 일치.
-
범위 밖: price 상한·오류 key 추정과 Comments 구현.
-
Community 오류 status/message key와 media upload 오류 fixture를 기록한다.
-
price 최대값이 제공되면 Audio와 같은 정책으로 갱신하고, 없으면 0 이상 정수만 유지한다.
-
목록·Sheet·form/media의 상태/action inventory를 작성하고 Page는 collection query/policy 조합, feature component는 Community 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.
Task 5.2 목록·collection Sheet
Goal 실행 P5-T2: 전용 detail route/GET 없는 active 목록과 collection Sheet mutation 흐름을 완성한다.
-
시작 조건:
P5-T1완료. -
완료 증거: 체크박스 전체, list/Sheet/router/cache/pin/deactivate test와 detail GET 0회 기록.
-
범위 밖: 댓글과 제공 계약에 없는 Community 검색.
-
active-only 목록의
page/size, loading·empty·error·retry와 URL query 보존을 test한다. 제공 계약에 없는 Communitysearchquery나 현재 page 한정 client 검색은 만들지 않는다. -
목록 item을 source로 Sheet를 열고 전용 detail GET을 0회 호출하는 test를 작성한다.
-
/community-posts/:postId,/editroute가 존재하지 않는 router test를 작성한다. -
Sheet의 조회·수정·고정/해제·비활성화가 목록 cache와 같은 server response를 사용한다.
-
고정/해제 후
isFixed/fixedAtUtc를 server 값으로 표시한다. -
soft delete 응답의
isFixed=false,fixedAtUtc=null을 contract test로 고정한다. -
soft delete 성공 시 Sheet 종료, active-only 목록 재조회·항목 제거, 성공 toast를 확인한다.
Task 5.3 게시글 form·첨부 media
Goal 실행 P5-T3: Community form, JPEG/PNG/GIF와 audio 첨부·재생 규칙을 완성한다.
-
시작 조건:
P5-T1,P5-T2완료. -
완료 증거: 체크박스 전체, serializer/form/media/file-boundary/player test,
OQ-009결정 기록. -
범위 밖: GIF 재인코딩, URL 갱신 전용 요청, Comments.
-
생성 payload에
isActive가 없고 일반 update/soft delete가 공통isActive규칙을 지키는 test를 작성한다. -
content, price 0 이상 정수, isAdult, isFixed와 optional image/audio를 test한다.
-
JPEG/PNG는 자유 ratio crop·최대 800px·no-upscale을 적용한다.
-
GIF는 Community에서만 허용하고 crop Dialog/canvas/re-encode 없이 원본 ratio·animation을 유지한다.
-
GIF 원본 width 800px은 허용하고 801px은 제출 전에 거부한다.
-
첨부 Audio는 Phase 1의 공통 audio file policy와
FileField를 Phase 3과 동일하게 조합해 MP3/AAC/M4A, x-m4a,1,024,000,000 bytes, WAV 거부 규칙을 재사용한다. -
첨부 Audio가 있으면 목록 Card/row와 Sheet에 공통 player를 렌더링한다.
-
media error가 detail/list refetch·URL 재발급·자동 play를 발생시키지 않는다.
-
사용자 새로고침이나 mutation cache invalidation으로 목록이 정상 재조회된 때만 새
audioSignedUrl을 사용한다. -
Community Sheet/form을 실제로 작성한 뒤
OQ-009의content상한 필요성을 판단하고, 구현 전 결정 문서를 갱신하거나 “상한 추가 없음”으로 종결한다.
Task 5.4 Community 반응형·접근성
Goal 실행 P5-T4: Community viewport capability와 Sheet/media/form 접근성을 검증한다.
-
시작 조건:
P5-T2,P5-T3의 활성 범위 완료. -
완료 증거: 체크박스 전체, focus trap/복귀·320px·keyboard·200% zoom·axe E2E 기록.
-
범위 밖: 모바일 등록·수정·고정·비활성화.
-
mobile은 목록·Sheet 조회·첨부 재생만 제공하고 등록·수정·고정·비활성화를 route/action policy로 차단한다.
-
desktop/tablet에는 전체 관리 흐름을 제공한다.
-
Sheet focus trap/복귀, keyboard media/form, 320px overflow, 200% zoom, axe critical·serious 0건을 확인한다.
Phase 5 Gate
Goal 실행 P5-GATE: Community 목록/Sheet CRUD·pin·media journey와 no-detail 불변식을 판정한다.
- 시작 조건:
P5-T1~P5-T4완료. - 완료 증거: 아래 명령과 Expected 통과 및 Phase 검증 기록.
- 범위 밖: 실패와 무관한 FanTalk/Comments 구현.
npm run test:run -- src/features/community-posts src/shared/validation
npm run e2e -- tests/e2e/community-post.spec.ts
npm run typecheck
npm run lint
npm run build
Expected: create → 목록 item Sheet 조회/수정 → pin/unpin → 첨부 재생 → soft delete가 통과하고 Community detail GET·detail route 호출은 0건이다.
Phase 6. FanTalk vertical slice
목표: 모든 viewport에서 FanTalk를 최신순·답변 상태로 조회하고 답변을 한 번 작성한 뒤 기존 답변만 수정한다.
Phase Goal P6: Task 6.1 → 6.3과 Phase 6 Gate로 FanTalk 조회·단일 답변·수정 slice를 완성한다.
- 시작 조건:
P2-T2workspace core 완료. - 완료 조건: 핵심 계약이 제공되면
P6-T1~P6-T3,P6-GATE완료. 미제공이면 Phase 제외/후속 결정 문서화로 종결. - 실행 순서: 계약 확인 → 목록/답변 → 반응형·접근성.
요구사항: FANTALK-001~008, PRD 9의 FanTalk 범위.
외부 의존: 목록·상세·답변 수정 endpoint/DTO, filter/sort, reply uniqueness의 원자적 강제와 중복 오류 계약. 핵심 계약이 없으면 이 Phase 전체를 추측 구현하지 않는다.
주요 Files:
- Create:
src/features/fan-talks/api/fan-talk-api.ts - Create:
src/features/fan-talks/model/types.ts - Create:
src/features/fan-talks/schemas/fan-talk-reply-schema.ts - Create:
src/features/fan-talks/pages/{FanTalkListPage,FanTalkDetailPage}.tsx - Create:
src/features/fan-talks/components/{FanTalkList,FanTalkListItem,FanTalkDetail,FanTalkReplyForm}.tsx - Create:
src/features/fan-talks/tests/fan-talk-contract.test.ts - Create:
src/features/fan-talks/tests/{fan-talk-list,fan-talk-reply}.test.tsx - Create:
tests/e2e/fan-talk.spec.ts - Modify:
src/app/router.tsx
Task 6.1 Phase 계약 확인
Goal 실행 P6-T1: FanTalk 목록·상세·수정·유일성·오류 계약과 component map을 확정한다.
-
시작 조건:
P2-T2완료, PRDFANTALK-001~008와 API Contract §8~9 확인. -
완료 증거: 체크박스 전체, 제공 계약 또는 Phase 제외/후속 결정의 세 문서 일치.
-
범위 밖: 임시 endpoint·placeholder DTO·production mock adapter.
-
목록·상세·답변 수정 endpoint, request/response DTO, page/filter/latest sort, ownership error를 기록한다.
-
답변 1개를 server가 원자적으로 강제하는 방식과 중복 생성 비2xx status/message key를 기록한다.
-
계약이 없으면 임시 endpoint·placeholder DTO·mock production adapter를 만들지 않고 Phase 제외/후속 결정을 문서화한다.
-
목록·상세·reply form의 상태/action inventory를 작성하고 Page는 route/query/policy 조합, feature component는 FanTalk 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.
Task 6.2 목록·답변 생성·수정
Goal 실행 P6-T2: 최신순/filter 목록과 답변 1회 생성·기존 답변 수정 흐름을 완성한다.
-
시작 조건:
P6-T1에서 핵심 계약 제공 확인. -
완료 증거: 체크박스 전체, list/detail/reply contract·UI test, 중복 제출/오류 복구,
OQ-009결정 기록. -
범위 밖: 답변 삭제·두 번째 답변과 계약 없는 network 동작.
-
기본 목록은 최신순 전체이며 전체/미답변/답변 완료 filter와 page를 URL에 보존한다.
-
loading·empty·error·retry와 direct detail/refresh를 test한다.
-
답변이 없을 때만 POST form을, 있으면 edit form만 표시하고 delete UI는 만들지 않는다.
-
빠른 두 번 제출에도 POST가 한 번만 호출되는 test를 작성한다.
-
답변 수정은 제공된 endpoint/reply identity만 사용한다.
-
server 중복 오류를 받으면 최신 detail을 재조회해 edit 상태로 전환하고 status/key를 추정 분기하지 않는다.
-
저장 중 중복 제출 차단, visible label, 오류 연결, 성공 live feedback을 test한다.
-
FanTalk reply form을 실제로 작성한 뒤
OQ-009의content상한 필요성을 판단하고, 구현 전 결정 문서를 갱신하거나 “상한 추가 없음”으로 종결한다.
Task 6.3 FanTalk 반응형·접근성
Goal 실행 P6-T3: 모든 viewport의 FanTalk 응대 capability와 접근성을 검증한다.
-
시작 조건:
P6-T2완료. -
완료 증거: 체크박스 전체, 320px keyboard viewport·keyboard-only·200% zoom·axe E2E 기록.
-
범위 밖: viewport별 기능 축소와 답변 삭제.
-
desktop/tablet/mobile 모두 조회·답변 작성·수정을 제공한다.
-
320px에서 keyboard가 reply input/submit을 가리지 않는 E2E를 작성한다.
-
keyboard-only filter/detail/create/edit, 200% zoom, axe critical·serious 0건을 확인한다.
Phase 6 Gate
Goal 실행 P6-GATE: FanTalk 단일 답변 불변식과 모든 viewport journey를 최종 판정한다.
- 시작 조건:
P6-T1~P6-T3활성 범위 완료. - 완료 증거: 아래 명령과 Expected 통과 또는 Phase 제외 결정의 검증 기록.
- 범위 밖: 계약 미제공 상태에서 network integration 완료 주장.
npm run test:run -- src/features/fan-talks
npm run e2e -- tests/e2e/fan-talk.spec.ts
npm run typecheck
npm run lint
npm run build
Expected: 미답변 조회 → 답변 1회 생성 → 기존 답변 수정이 모든 viewport에서 통과하며 두 번째 reply 생성과 delete UI가 없다.
Phase 7. Comments vertical slice
목표: Audio detail과 Community Sheet 양쪽에서 같은 2단계 댓글 UX를 제공하고 작성자별 수정·soft delete 권한을 일관되게 적용한다.
Phase Goal P7: Task 7.1 → 7.4와 Phase 7 Gate로 두 target의 2단계 Comments slice를 완성한다.
- 시작 조건:
P3-T2Audio detail과P5-T2Community Sheet 완료. - 완료 조건: 핵심 계약이 제공되면
P7-T1~P7-T4,P7-GATE완료. 미제공이면 Phase 제외/후속 결정 문서화로 종결. - 실행 순서: 계약 확인 → target/thread → 권한 action → 반응형·접근성.
요구사항: COMMENT-001~006, PRD 9의 Comments 범위.
외부 의존: Audio·Community 댓글 목록/작성/수정/soft delete endpoint·DTO, 2단계 강제, fan 댓글 삭제 권한 오류. 핵심 계약이 없으면 이 Phase 전체를 추측 구현하지 않는다.
주요 Files:
- Create:
src/features/comments/api/comment-api.ts - Create:
src/features/comments/model/{types,comment-target}.ts - Create:
src/features/comments/schemas/comment-schema.ts - Create:
src/features/comments/components/{CommentThread,CommentForm,CommentActions,CommunityPostCommentsSheet}.tsx - Create:
src/features/comments/tests/comment-contract.test.ts - Create:
src/features/comments/tests/{comment-thread,comment-permissions}.test.tsx - Create:
tests/e2e/comments.spec.ts - Modify:
AudioContentDetailPage.tsx,CommunityPostSheet.tsx
Task 7.1 Phase 계약 확인
Goal 실행 P7-T1: 두 댓글 target의 CRUD·2단계·권한 오류 계약과 component map을 확정한다.
-
시작 조건:
P3-T2,P5-T2완료, PRDCOMMENT-001~006과 API Contract §9 확인. -
완료 증거: 체크박스 전체, 제공 계약 또는 Phase 제외/후속 결정의 세 문서 일치.
-
범위 밖: endpoint 이름 추정과 client-only permission 완료 주장.
-
Audio·Community target별 endpoint, query, DTO, page, 작성/수정/soft delete 응답을 기록한다.
-
root/direct reply 정확히 2단계인 server rule과 fan content 삭제 권한 오류 status/message key를 기록한다.
-
계약이 없으면 target endpoint를 이름만 보고 추정하거나 client-only permission을 완료로 간주하지 않는다.
-
Audio detail·Community Sheet 진입별 thread/form/action inventory를 작성하고 host Page/Sheet는 target·query 조합, Comments component는 thread·permission 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.
Task 7.2 target adapter·2단계 thread
Goal 실행 P7-T2: Audio/Community target adapter와 정확히 2단계인 comment thread 조회를 완성한다.
-
시작 조건:
P7-T1에서 핵심 계약 제공 확인. -
완료 증거: 체크박스 전체, target contract/thread/state/page test와 Community 상태 보존 기록.
-
범위 밖: 작성자별 mutation action.
-
UI target은 Audio와 Community를 명시적으로 구분하고 각 제공 endpoint로만 요청하는 contract test를 작성한다.
-
Community 상세 GET 없이 목록 item의
characterId/postId로 Comments Sheet를 연다. -
Comments Sheet 종료 후 Community page·scroll 상태를 보존한다.
-
root와 direct reply만 렌더링하고 reply에는 reply action이 없음을 test한다.
-
long content, loading·empty·error·retry, page 갱신을 양 target에서 test한다.
Task 7.3 작성자별 action
Goal 실행 P7-T3: AI Character와 fan 작성자별 수정·soft delete 권한을 일관되게 적용한다.
-
시작 조건:
P7-T1,P7-T2완료. -
완료 증거: 체크박스 전체, permission/mutation/session/read-only test,
OQ-009결정 기록. -
범위 밖: fan edit와 reply의 reply.
-
AI Character 작성 root/reply에는 edit·soft delete를 제공한다.
-
fan 작성 root/reply에는 edit를 제공하지 않고 운영 soft delete만 제공한다.
-
fan edit request는 type과 UI 양쪽에서 생성할 수 없음을 test한다.
-
delete 전 대상과 영향을 확인하고 server 계약에 따라 tombstone 또는 목록 갱신을 적용한다.
-
Character workspace read-only 정책이 모든 comment mutation도 차단하는 test를 작성한다.
-
중복 제출, server permission 오류, session 401/403이 공통 정책을 따르는지 test한다.
-
Comment thread/form을 실제로 작성한 뒤
OQ-009의content상한 필요성을 판단하고, 구현 전 결정 문서를 갱신하거나 “상한 추가 없음”으로 종결한다.
Task 7.4 Comments 반응형·접근성
Goal 실행 P7-T4: 모든 viewport의 Comments CRUD capability와 thread/form/dialog 접근성을 검증한다.
-
시작 조건:
P7-T2,P7-T3완료. -
완료 증거: 체크박스 전체, 320px·keyboard-only·focus 복귀·200% zoom·axe E2E 기록.
-
범위 밖: 모바일 기능 축소.
-
desktop/tablet/mobile 모두 조회·작성·수정·soft delete를 제공한다.
-
320px에서 긴 댓글, reply indentation, action menu, keyboard 입력이 overflow하지 않는 E2E를 작성한다.
-
keyboard-only root/reply 작성·수정·delete dialog, focus 복귀, 200% zoom, axe critical·serious 0건을 확인한다.
Phase 7 Gate
Goal 실행 P7-GATE: 두 target의 2단계 CRUD·권한·모바일 journey를 최종 판정한다.
- 시작 조건:
P7-T1~P7-T4활성 범위 완료. - 완료 증거: 아래 명령과 Expected 통과 또는 Phase 제외 결정의 검증 기록.
- 범위 밖: 계약 미제공 상태에서 network integration 완료 주장.
npm run test:run -- src/features/comments
npm run e2e -- tests/e2e/comments.spec.ts
npm run typecheck
npm run lint
npm run build
Expected: Audio와 Community 두 진입점에서 2단계 댓글 CRUD·권한·모바일 흐름이 통과하고 reply의 reply 및 fan edit request는 생성되지 않는다.
Phase 8. 교차 회귀·인수인계
목표: 새 기능을 추가하지 않고 활성 릴리스 범위 전체가 PRD, API Contract, 보안, 반응형, 접근성 기준을 만족한다는 최신 증거를 남긴다.
Phase Goal P8: Task 8.1 → 8.3과 Phase 8 Gate로 활성 릴리스 범위의 최종 인수 증거를 완성한다.
- 시작 조건: 활성 범위의
P0-GATE~P7-GATE완료 또는 명시적 제외 결정. - 완료 조건:
P8-T1~P8-T3,P8-GATE완료, 전체 자동 Gate 0 failure/0 error와 수동 검증 기록. - 실행 순서: 교차 journey/오류 → 반응형/접근성/보안 → 문서/품질 → 전체 Gate.
주요 Files:
- Create:
tests/e2e/{resource-workflows,error-mapping,responsive-capabilities,accessibility}.spec.ts - Modify:
README.md - Modify:
docs/20260725_AI캐릭터관리자웹/{prd.md,api-contract.md,plan-task.md}only when actual implementation decision differs.
Task 8.1 교차 journey·오류 회귀
Goal 실행 P8-T1: 활성 도메인의 교차 journey, 오류 매핑과 serializer/security 불변식을 회귀 검증한다.
-
시작 조건: 활성 범위의 Phase 0~7 Gate 완료 또는 제외 결정.
-
완료 증거: 체크박스 전체, resource-workflows/error-mapping E2E와 request 0회·serializer fixture 기록.
-
범위 밖: 새 기능과 계약 미제공 제외 범위의 가짜 journey.
-
login → Character select → Audio immediate/scheduled create/play → Series link/order → Community Sheet → FanTalk → Comments의 활성 범위 journey를 검증한다.
-
400/401/403/404/405/415/500 fixture가 공통 한국어 message와 올바른 route/session 처리를 하는지 검증한다.
-
Character·Audio·Series soft delete는 목록 이동, Community soft delete는 Sheet 종료·목록 제거로 끝나는지 검증한다.
-
inactive Character workspace에서 모든 하위 mutation request가 0건인지 검증한다.
-
media error로 Audio/Community GET·URL 재발급·자동
play()가 발생하지 않는지 검증한다. -
생성·일반 수정·soft delete serializer 불변식을 모든 도메인 fixture에서 다시 검증한다.
Task 8.2 반응형·접근성·보안 회귀
Goal 실행 P8-T2: 지원 viewport/browser의 capability, 접근성과 민감정보 비기록을 전역 검증한다.
-
시작 조건:
P8-T1완료. -
완료 증거: 체크박스 전체, table-driven E2E·axe·수동 browser/keyboard/zoom/security·UX 검색 기록.
-
범위 밖: 다크 모드 구현과 외부 WCAG 인증.
-
PRD
9기능 matrix를 table-driven E2E data로 검증한다. -
320, 640, 768, 1024, 1280px와 landscape에서 overflow·가려진 keyboard·44px target을 확인한다.
-
desktop Chrome/Edge/Safari와 mobile Chrome/Safari 최신 2개 주요 버전 범위를 실제 지원 환경에서 확인한다.
-
모든 핵심 route에서 axe critical·serious 위반 0건을 확인한다.
-
keyboard-only, first-error-focus, dialog focus 복귀, skip link, live region, reduced motion, 200% zoom을 수동 검증한다.
-
system dark mode에서도 밝은 token을 유지하고 theme toggle이 없음을 검증한다.
-
JWT, password, signed URL, file body가 log·storage·분석 event에 남지 않는지 검증한다.
-
각 Page가 승인된 component map대로 route/query/permission과 component 조합만 담당하고, domain 상호작용이 feature/shared component test로 분리됐는지 review한다.
-
PRD
10.9의 UX 검증 검색을 다시 실행하고 채택·제외 결과를 기록한다.
Task 8.3 문서·품질 Gate
Goal 실행 P8-T3: 요구사항 추적, 외부 의존·Open Question, README와 검증 기록을 실제 구현에 맞춘다.
-
시작 조건:
P8-T1,P8-T2완료. -
완료 증거: 체크박스 전체, PRD 수용 기준별 증거 연결, 독립 code review 수정과 관련 Gate 재실행 기록.
-
범위 밖: 결정되지 않은 계약을 문서상 확정하는 행위.
-
활성 범위의 P0 외부 의존이 0건인지, 아니면 구현 전에 명시적으로 후속/제외 결정됐는지 확인한다.
-
OQ-009를 각 도메인별 확정 또는 “상한 추가 없음”으로 종결하고 중복 checklist를 남기지 않는다. -
OQ-010감사 로그 UI가 현재 릴리스 non-goal임을 결정 기록과 맞춘다. -
실제 구현과 다른 결정은 PRD 결정 기록 → API Contract → plan 순으로 갱신한다.
-
PRD 수용 기준마다 자동 test 또는 수동 검증 증거를 연결한다.
-
README에 install, env, run, test, build, 지원 브라우저, 알려진 backend 제약을 기록한다.
-
plan 하단 검증 기록에 무엇을/왜/어떻게와 실제 명령·성공/실패/불가 사유를 누적한다.
-
별도 code review를 받고 지적사항 수정 후 관련 Phase Gate와 전체 Gate를 다시 실행한다.
Phase 8 Gate
Goal 실행 P8-GATE: 활성 릴리스 전체의 install·type·lint·unit·E2E·build·금지 패턴을 최종 판정한다.
- 시작 조건:
P8-T1~P8-T3완료. - 완료 증거: 아래 명령이 0 failure/0 error이고 Expected 및 수동 검증 결과가 최종 검증 기록에 누적됨.
- 범위 밖: Gate 통과를 위한 test 삭제·완화, production placeholder, 계약 추정.
set -e
assert_no_match() {
if rg -n "$@"; then
return 1
else
rg_status=$?
[ "$rg_status" -eq 1 ]
fi
}
npm ci
npm run typecheck
npm run lint
npm run test:run
npm run e2e
npm run build
assert_no_match 'TODO|TBD|FIXME' src tests
assert_no_match "externalCharacterId|SUNDAY|MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|state.?[:=].?['\\\"]OPEN" src \
--glob '!**/*.test.*' --glob '!**/*.spec.*' --glob '!**/__tests__/**'
Expected: 전체 자동 Gate가 0 failure/0 error이고, assert_no_match는 no-match인 rg exit 1만 성공으로 바꾸며 rg 실행 오류는 실패로 전파한다. production source의 금지 값과 미완료 placeholder는 0건이어야 한다. 부정 test fixture의 금지 문자열은 허용하며 production 결과와 구분한다.
5. 요구사항 추적표
| Phase | PRD 범위 | 집중 test |
|---|---|---|
| 0 | React+TypeScript+Vite, 지원 브라우저 기반 | src/app/App.test.tsx, tests/e2e/smoke.spec.ts |
| 1 | AUTH-001~013, UX-001~002, FILE의 domain-neutral component mechanics, §7, §10 공통, §11.1~11.2, §12 |
src/shared, src/features/auth, tests/e2e/auth.spec.ts |
| 2 | CHAR-001~014, Character 관련 FILE, §7, §9 |
src/features/characters, tests/e2e/character-workspace.spec.ts |
| 3 | AUDIO-001~028, Audio 관련 FILE, §9 |
src/features/audio-contents, tests/e2e/audio-content.spec.ts |
| 4 | SERIES-001~013, Series 관련 FILE, §9 |
src/features/series, tests/e2e/series.spec.ts |
| 5 | COMMUNITY-001~011, Community 관련 FILE, §9 |
src/features/community-posts, tests/e2e/community-post.spec.ts |
| 6 | FANTALK-001~008, §9 |
src/features/fan-talks, tests/e2e/fan-talk.spec.ts |
| 7 | COMMENT-001~006, §9 |
src/features/comments, tests/e2e/comments.spec.ts |
| 8 | §9 |
전체 unit/integration/E2E/build |
FILE-001~015의 domain-neutral component mechanics는 Phase 1에서 먼저 만든다. Character·Audio·Series·Community Phase는 자신의 allowed type·crop profile·GIF 예외와 multipart 흐름을 소유하면서 공통 mechanics를 조합·검증하고, Phase 8에서 전체 matrix를 회귀 검증한다.
6. 구현 완료 정의
- Phase 0~7의 활성 범위 Gate와 Phase 8 전체 Gate가 최신 실행에서 통과한다.
- 모든 확정 요구사항이 구현, 명시적 non-goal, 또는 결정 기록이 있는 후속 범위 중 하나로 추적된다.
- 외부 의존을 추정 endpoint·placeholder DTO·임시 production mock으로 우회하지 않았다.
- 생성·일반 수정·soft delete payload와 enum 보정 contract test가 통과한다.
- desktop/tablet/mobile 기능 matrix가 route와 action policy 양쪽에서 일치한다.
- 파일 MIME·크기·crop·no-upscale·GIF·오디오 경계 test가 통과한다.
- 모든 화면이 Page → feature component → shared/shadcn component 조합 규칙을 따르고 component별 독립 test를 가진다.
- media error, upload 취소·재시도, 중복 제출, session 오류가 검증된다.
- keyboard, label, focus, contrast, reduced motion, 200% zoom, axe 기준이 충족된다.
- 문서와 실제 구현의 알려진 차이가 0건이다.
- 검증 기록이 실제 명령과 결과를 포함해 누적돼 있다.
7. 검증 기록
구현 단계마다 아래 형식으로 누적하고 기존 기록을 삭제하거나 덮어쓰지 않는다.
### N차 구현 또는 수정 — YYYY-MM-DD
- 무엇을: 완료한 Phase와 주요 결과
- 왜: 해당 범위와 결정 근거
- 어떻게:
- `실행 명령` — 성공/실패와 핵심 수치
- 수동 검증 항목 — 성공/실패/불가 사유
- 남은 항목: 외부 의존, 후속 범위, 없음 중 하나
계획 재작성 — 2026-07-26
- 무엇을: 기존 18개 선형 Task를 프로젝트 세팅, 공통 플랫폼·인증/인가·컴포넌트 기반, 6개 도메인 vertical slice, 교차 회귀의 9개 Phase로 재구성했다. Phase 1 공통 컴포넌트 소비처 Matrix와 각 도메인 component map을 추가했다.
- 왜: 프로젝트 세팅을 분리하고 공통 기반을 도메인보다 먼저 완결하며, 각 Phase를 기능·오류·반응형·접근성까지 독립 검증하기 위해서다. 미결·외부 의존은 전역 blocker 대신 소유 도메인에서 결정·제외하도록 했다.
- 어떻게:
rg -c '^## Phase [0-8]\.' plan-task.md와rg -c '^### Phase [0-8] Gate$' plan-task.md— Phase 9개, Gate 9개 확인.rg -c '^- \[ \].*component map을 확정한다' plan-task.md— 도메인 component map 6개 확인.- 요구사항 range와 Phase 1 shared component Matrix
rg검사 — 모든 range와 12개 shared component 이름 확인. - Markdown fence
awk검사 — 24개, 짝수로 균형 확인. git diff --check— 성공, whitespace 오류 0건.git -c core.quotePath=false diff --name-only—docs/20260725_AI캐릭터관리자웹/plan-task.md1개만 확인, 애플리케이션 코드 변경 0건.- 애플리케이션 test/build — 이번 단계는 계획 전용이고 아직
package.json이 없어 실행하지 않음.
- 남은 항목: 후속 구현 전체와 각 도메인에 기록한 backend 외부 의존 계약.
Task 1.1 구현 — 2026-07-26
- 무엇을: PRD
10.1~10.9기반의 밝은 관리자 디자인 시스템을DESIGN.md와src/styles/globals.css에 문서화·구현하고, Tailwind CSS Vite 플러그인과 shadcn/ui CSS variable modecomponents.json을 최소 구성했다.StatusBadge와IconOnlyAction원시 컴포넌트만 추가했다. - 왜: Phase 1 이후 UI가 raw hex나 임의 스타일을 반복하지 않고,
#00BDF7고정 브랜드 색과 접근성 대비·light-only 범위를 테스트로 잠그기 위해서다. - 어떻게:
python3 .opencode/skills/ui-ux-pro-max/scripts/search.py "enterprise internal admin console data tables forms file upload operational dashboard neutral compact" --design-system --variance 2 --motion 2 --density 9 -p "AI Character Admin" -f markdown— 성공. 채택: dense operational dashboard, subtle motion, status/focus/reduced-motion/accessibility 항목. 제외: green palette, white primary foreground, dark mode, remote Fira fonts, oversized landing typography, glass/continuous animation, GSAP transition.python3 .opencode/skills/ui-ux-pro-max/scripts/search.py "animation accessibility z-index loading" --domain ux -n 12— 성공, ux-guidelines.csv에서 12건 반환. 채택: 300ms 초과 작업의 loading feedback, semantic z-index scale, reduced motion, 150-300ms micro-interaction, decorative infinite animation 금지. 제외/후속: lazy-loaded media, loading buttons, skeleton, route loading은 Task 1.1에 async page·media·form submit·router surface가 없어 후속 실제 소비 Task에서 적용한다.- RED
npm run test:run -- src/styles/__tests__/design-system.test.ts src/shared/ui/__tests__/status-badge.test.tsx src/shared/ui/__tests__/icon-only-action.test.tsx src/app/App.test.tsx— 기대 실패 확인.DESIGN.md,src/styles/globals.css,StatusBadge,IconOnlyAction미구현으로 3 files failed / 1 passed. - GREEN
npm run test:run -- src/styles/__tests__/design-system.test.ts src/shared/ui/__tests__/status-badge.test.tsx src/shared/ui/__tests__/icon-only-action.test.tsx src/app/App.test.tsx— 성공, 4 files / 7 tests passed. npm run test:run -- src/styles/__tests__/design-system.test.ts src/shared/ui/__tests__/status-badge.test.tsx src/shared/ui/__tests__/icon-only-action.test.tsx src/app/App.test.tsx src/shared/config/env.test.ts— 성공, 5 files / 10 tests passed.npm run typecheck— 성공.npm run lint— 성공.npm run e2e -- --project=chromium tests/e2e/smoke.spec.ts— 성공, Chromium 1 test passed. Rootlang="ko"와main의AI 캐릭터 관리자를 실제 브라우저에서 확인했다.npm run build:dev— 성공, Vite development build 완료.npm run build:prod— 성공, Vite production build 완료.- LSP diagnostics — 변경 TypeScript/TSX 파일 모두 오류 0건.
rg -n "#[0-9A-Fa-f]{6}|rgb\(|hsl\(|oklch\(" src --glob '!src/styles/globals.css' --glob '!**/*.test.*' --glob '!**/__tests__/**'— no output, production feature code raw color 0건.rg -n "\.dark\b|ThemeProvider|theme toggle|next-themes|prefers-color-scheme" src components.json package.json --glob '!**/*.test.*' --glob '!**/__tests__/**'— no output, dark/theme integration 0건.
- 남은 항목: Task 1.1 없음. Auth, router, API, shell, file/media 컴포넌트와 추가 shadcn component는 후속 Task에서 실제 소비 시점에만 추가한다.
Task 1.1 리뷰 차단 수정 — 2026-07-26
- 무엇을: Task 1.1 spec/code-quality review blocker를 수정했다. foreground 대비 test를 추가하고, PRD
10.9UX 검색 기록을 보강했으며, app tsconfig의 Node globals 오염을 test 전용 tsconfig로 분리했다.IconOnlyAction의 중복 accessible description과 중복 CSS utility를 제거하고, status/link/info semantic token을 Tailwind theme에 노출했다. - 왜: Task 1.1이 spec review와 code-quality review를 통과하려면 core foreground contrast, UX 검색 증거, production/browser tsconfig 순도, tooltip 접근성, Tailwind semantic token 노출이 모두 필요하기 때문이다.
- 어떻게:
- RED
npm run test:run -- src/styles/__tests__/design-system.test.ts— 기대 실패 확인. 신규 core foreground contrast test가Missing --foreground로 실패해--foreground/--background/--cardalias contrast 검증 공백을 드러냈다. python3 .opencode/skills/ui-ux-pro-max/scripts/search.py "animation accessibility z-index loading" --domain ux -n 12— 성공, ux-guidelines.csv에서 12건 반환. 채택: loading feedback, stacking context/z-index scale, reduced motion, 150-300ms duration. 제외/후속: lazy loading, skeleton/loading buttons, route loading은 Task 1.1에 실제 async/media/form/router surface가 없어 후속 소비 시점에 적용한다.- GREEN
npm run test:run -- src/styles/__tests__/design-system.test.ts src/shared/ui/__tests__/icon-only-action.test.tsx— 성공, 2 files / 7 tests passed. npm run test:run -- src/styles/__tests__/design-system.test.ts src/shared/ui/__tests__/status-badge.test.tsx src/shared/ui/__tests__/icon-only-action.test.tsx src/app/App.test.tsx src/shared/config/env.test.ts— 성공, 5 files / 12 tests passed.npm run typecheck— 성공.tsconfig.app.json에는 Node type을 두지 않고tsconfig.test.json에만 test용 Node/Jest-DOM type을 둔다.npm run lint— 성공.npm run build:dev— 성공, Vite development build 완료.npm run build:prod— 성공, Vite production build 완료.npm run e2e -- --project=chromium tests/e2e/smoke.spec.ts— 성공, Chromium 1 test passed. Rootlang="ko"와main의AI 캐릭터 관리자smoke behavior 유지 확인.- LSP diagnostics —
src/shared/ui/icon-only-action.tsx,src/shared/ui/__tests__/icon-only-action.test.tsx,tsconfig.json,tsconfig.app.json,tsconfig.test.json오류 0건.src/styles디렉터리 진단 오류 0건. rg -n '"node"' tsconfig.app.json— no output, production/browser app tsconfig Node type 0건.rg -n "#[0-9A-Fa-f]{6}|rgb\(|hsl\(|oklch\(" src --glob '!src/styles/globals.css' --glob '!**/*.test.*' --glob '!**/__tests__/**'— no output, production feature code raw color 0건.rg -n "\.dark\b|ThemeProvider|theme toggle|next-themes|prefers-color-scheme" src components.json package.json --glob '!**/*.test.*' --glob '!**/__tests__/**'— no output, dark/theme integration 0건.
- RED
- 남은 항목: Task 1.1 없음.
.gitignore는 작업 전 dirty 상태였고 이번 수정에서 건드리지 않았다.
Task 1.2 구현 — 2026-07-26
- 무엇을:
sessionStorage기반 ADMIN session adapter, Zod envelope parser와ApiError/AccessDeniedErrorAPI client, pagination helper, MSW test server, TanStack Query client/provider를 최소 구성했다. API client는 React에 의존하지 않고 token reader·clear·auth-expired callback을 주입받으며, API 요청을 console에 기록하지 않는다. - 왜: 이후 로그인·보호 route·도메인 API가 동일한 Korean locale, Bearer, 401/403, 오류 envelope, page size, retry 정책을 재구현하지 않도록 공통 경계를 먼저 고정하기 위해서다.
- 어떻게:
npm install @tanstack/react-query zod및npm install --save-dev msw— 성공. Task 1.2에서 실제 소비하는 의존성만 추가.- RED
npm run test:run -- src/features/auth/model/auth-session-storage.test.ts src/shared/api/__tests__/pagination.test.ts src/shared/api/__tests__/query-client.test.ts src/shared/api/__tests__/client.test.ts— 기대 실패. 아직 없는 session/API/pagination/query 모듈의 import를 해결하지 못해 4 files failed. - GREEN 같은 focused command — 성공, 4 files / 25 tests passed. success envelope 2형태, locale/Bearer, 5개 HTTP 오류 status, concurrent 401, 403, no-console, pagination, retry와 session adapter를 확인.
- test file 순도 검토 뒤 client contract와 authenticated request contract를 분리한 focused command — 성공, 5 files / 25 tests passed. 각 TypeScript test file은 200 pure LOC 이하로 유지.
npm run typecheck— 성공.npm run lint— 성공.npm run build:dev및npm run build:prod— 성공.- LSP diagnostics —
src/features/auth/model/auth-session-storage.ts,src/shared/api/*, 해당 test 및src/main.tsx,src/shared/test/*오류 0건. npm run dev후 Playwright로http://127.0.0.1:8888/접근 — 성공. 페이지 titleAI 캐릭터 관리자와main > h1shell 렌더를 확인했다. console은 기존 favicon 미구현에 따른/favicon.ico404만 1건이었다.npm run test:run전체 실행은 Vitest가tests/e2e/smoke.spec.ts를 함께 수집해 Playwright의test()호출 오류로 실패했다. Task 1.2 focused Vitest suite와 기존 unit suite 37 tests는 통과했으며, E2E 파일 분리는 이번 Task 범위 밖으로 유지한다.
- 남은 항목: Task 1.2 없음. login request payload/session React state/logout UI, protected route와 access-denied page는 Task 1.3~1.4에서 연결한다.
Task 1.2 리뷰 차단 수정 — 2026-07-26
- 무엇을: 401 처리 latch를 client lifetime이 아닌 in-flight protected request burst 단위로 재설정하고, 모든 caller-supplied
Authorization을 제거한 뒤 현재 session의 보호 요청 Bearer만 다시 설정했다. app/test TypeScript project를 분리하고 Vitest가 Playwright E2E spec을 수집하지 않도록 했다. - 왜: 이후 로그인 후 동일 client가 다시 401을 받는 경우에도 session clear·redirect가 동작해야 하며, public/login 요청이 caller header로 인증 정보를 유출하거나 unit test runner가 E2E runner를 실행하면 안 되기 때문이다.
- 어떻게:
- RED
npm run test:run -- src/shared/api/__tests__/client-auth.test.ts— 기대 실패, caller-supplied Bearer가 login에 전달되고 두 번째 concurrent 401 burst의 callback 횟수가 1회로 고정됨을 확인했다. - GREEN 같은 command — 성공, 1 file / 6 tests passed. public Authorization 제거와 burst별 401 callback 재실행을 확인했다.
npm run test:run -- src/styles/__tests__/design-system.test.ts src/shared/ui/__tests__/status-badge.test.tsx src/shared/ui/__tests__/icon-only-action.test.tsx src/app/App.test.tsx src/shared/config/env.test.ts src/features/auth/model/auth-session-storage.test.ts src/shared/api/__tests__/pagination.test.ts src/shared/api/__tests__/query-client.test.ts src/shared/api/__tests__/client.test.ts src/shared/api/__tests__/client-auth.test.ts— 성공, 10 files / 37 tests passed.npm run test:run— 성공, 10 files / 37 tests passed.tests/e2e/**Playwright spec은 Vitest default exclude를 보존한 채 제외했다.npm run typecheck,npm run lint,npm run build:dev,npm run build:prod— 모두 성공.npx tsc --project tsconfig.app.json --listFiles --pretty false | rg 'src/shared/test/(setup|server)\.ts$'— no output.tsconfig.test.jsonlistFiles에는src/shared/test/setup.ts,server.ts포함 확인.npm run e2e -- --project=chromium tests/e2e/smoke.spec.ts— 성공, Chromium 1 test passed.- LSP diagnostics — 변경 TypeScript와 tsconfig/Vite config 오류 0건.
git diff --check— 성공.
- RED
- 남은 항목: Task 1.2 없음. 이후 Task 1.3~1.4에서 login/session UI와 route 연결만 구현한다.
Task 1.3 구현 — 2026-07-26
- 무엇을: 로그인 request/response contract,
sessionStorage전용 ADMIN session provider/model, 실패해도 local session을 제거하는 logout 흐름, visible label·field error 연결·first invalid focus를 갖춘LoginPage를 추가했다. Router, ProtectedRoute, Admin shell,/loginroute wiring은 Task 1.4 범위로 남겨 두었다. - 왜: Task 1.4 route 조합 전에 로그인 API 경계, session 복원/제거 규칙, 저장소 금지 규칙, logout 실패 정책, 접근 가능한 로그인 form contract를 독립 테스트로 고정하기 위해서다.
- 어떻게:
- RED
npm run test:run -- src/features/auth/tests/auth-api.test.ts src/features/auth/tests/auth-session.test.tsx src/features/auth/tests/login-page.test.tsx— 기대 실패. 아직 없는auth-api,auth-session,LoginPageimport 3건으로 3 files failed. - GREEN 같은 focused command — 성공, 3 files / 17 tests passed. login JSON body·Authorization 없음·refresh 0건, token/role invalid reject, sessionStorage-only restore, logout success/non-2xx/network failure 제거·
/loginnavigation·failure warning, LoginPage label/error/focus를 확인했다. - refactor 후 focused command 재실행 — 성공, 3 files / 17 tests passed.
npm run test:run— 성공, 13 files / 54 tests passed.npm run typecheck— 성공.npm run lint— 성공. 중간에 Fast Refresh warning 1건을auth-session-context.ts분리로 제거한 뒤 재실행했다.npm run build:dev— 성공, Vite development build 완료.npm run build:prod— 성공, Vite production build 완료.npm run e2e -- tests/e2e/smoke.spec.ts— 성공, chromium/webkit/mobile-chrome/mobile-safari 4 tests passed. 현재 App root shell은 Task 1.4 전 상태로 유지됨을 확인했다.GIT_MASTER=1 git diff --check— 성공.- LSP diagnostics —
auth-api.ts,auth-session-context.ts,login-schema.ts,LoginPage.tsx, 3개 auth test 파일 오류 0건.auth-session.tsx개별 파일 진단은 반복 timeout이었고,src/features/auth/model디렉터리 진단 0건 및npm run typecheck성공으로 보완 확인했다. - changed file pure LOC —
auth-api.ts27,auth-session-context.ts24,auth-session.tsx53,login-schema.ts6,LoginPage.tsx118,auth-api.test.ts89,auth-session.test.tsx130,login-page.test.tsx36으로 모두 200 LOC 이하.
- RED
- 남은 항목: Task 1.3 없음. LoginPage browser visual QA는 아직 route/App surface에 연결하지 말라는 Task 1.3 범위 제한 때문에 RTL과 기존 root smoke로만 검증했고, 실제
/loginroute browser QA는 Task 1.4에서 수행한다.
Task 1.4 Wave 2 구현 — 2026-07-26
- 무엇을:
/ai-characters에 ADMIN session 전용 shell,/access-deniedroute, 401 login redirect, 403 access denied redirect, logout control, breadcrumb, skip link, mobile menu, 명시적 Phase 2 empty state를 연결했다. - 왜: Phase 2 도메인 데이터 구현 전에 보호 route·권한 오류·session expiry·shell 접근성 contract를 먼저 고정하기 위해서다.
- 어떻게:
- RED
npm run test:run -- src/app/App.test.tsx— 기대 실패, 기존 placeholder shell 때문에 신규 shell/401/403/mobile/logout 기대 6건 실패. - GREEN 같은 focused command — 성공, 1 file / 9 tests passed.
npm run typecheck— 성공.npm run lint— 성공. 중간에 effect 내부 동기setStatelint를 비동기 성공 경로로 옮긴 뒤 재실행했다.npm run test:run -- src/app/App.test.tsx src/shared/api/__tests__/client-auth.test.ts src/styles/__tests__/design-system.test.ts— 성공, 3 files / 21 tests passed.npm run build:prod— 성공.- Playwright 수동 QA —
/login, ADMIN session/ai-characters, mobile menu Escape focus return, logout/login, unauth/ai-charactersredirect, 403/access-denied와 protected empty state 미노출을 확인했다. - LSP diagnostics —
browser-location.ts오류 0건.App.tsx,App.test.tsx,route-paths.ts개별 fresh 진단은 반복 timeout이었고, TypeScript LSP alive 상태와npm run typecheck성공으로 보완 확인했다. - changed file pure LOC —
App.tsx214,App.test.tsx136,browser-location.ts24,route-paths.ts6.App.tsx는 200-250 경고 구간이므로 다음 shell 확장 전 분리 후보로 남긴다.
- RED
- 남은 항목: Task 1.4의 320px/200% zoom E2E와 axe critical·serious 0건 확인은 이번 Wave 2 기대 범위 밖이라 체크하지 않았다.
Task 1.6 구현 — 2026-07-27
- 무엇을: 공통
FileField, file validation, audio file policy, image policy contract,ImageCropDialog,UploadProgress,AdminAudioPlayer,AudioPlaybackProvider를 공유 레이어에 추가하고DESIGN.mdSection 5에 primitive contract를 기록했다. - 왜: Phase 2 이후 도메인 form이 endpoint/query cache/domain DTO 없이 파일·crop·upload 표시·audio playback mechanics를 재사용하도록 하기 위해서다.
- 어떻게:
- RED
npm run test:run -- src/shared/validation/file-media-policy.test.ts src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/file-field.test.tsx src/shared/ui/__tests__/image-crop-dialog.test.tsx src/shared/ui/__tests__/upload-progress.test.tsx src/shared/ui/__tests__/admin-audio-player.test.tsx src/shared/ui/__tests__/file-media-dependency-boundary.test.ts— 기대 실패. 아직 없는 shared file/media module import와 dependency boundary 대상 파일 부재로 7 files failed. - GREEN 같은 command — 성공, 7 files / 19 tests passed. 주입
maxBytes10MB 경계, audio1,024,000,000 bytes,.m4a + audio/x-m4a, WAV 거부, no codec sniffing, no-upscale crop output, keyboard/button 대안, upload callback-only, audio no autoplay/refetch/download, signed URL no log/storage, dependency boundary를 확인했다. - crop dependency 결정:
package.json에 crop dependency가 없고 이번 Phase는 raw pointer drag/pinch가 필수가 아니므로 새 dependency 없이 native button/range/keyboard 이동을 선택했다. Canvas는 결과File생성 함수에만 사용한다. npm run test:run -- src/shared— 성공, 21 files / 57 tests passed.npm run typecheck,npm run lint,npm run build— 모두 성공.- LSP diagnostics —
src/shared와src/shared/uidirectory 진단 0건. 일부 개별 file fresh 진단은 timeout이었고 TypeScript LSP alive,npm run typecheck,npm run build성공으로 보완 확인했다. - changed file pure LOC — 최대
admin-audio-player.tsx122,image-crop-dialog.tsx118로 모두 200 LOC 이하.
- RED
Goal 실행 계약 보완 — 2026-07-27
- 무엇을: 완료된 Phase 0
1을 포함한 Phase 08, 구현 Task 34개, Phase Gate 9개에 goal ID, objective, 시작 조건, 완료 증거와 범위 밖을 추가했다. goal의 단일 활성 작업, 완료·차단 판정, 외부 계약 처리와 검증 기록 규칙을 공통 운영 규칙으로 명시했다. - 왜: goal 기능이 Task 하나를 독립 실행 단위로 이어서 수행하고, 일부 구현만으로 완료하거나 외부 계약을 추정하지 않으며, Phase Gate까지 객관적인 증거로 종결할 수 있게 하기 위해서다.
- 어떻게:
rg -c '^## Phase [0-8]\.' docs/20260725_AI캐릭터관리자웹/plan-task.md— Phase 9개 확인.rg -c '^\*\*Phase GoalP[0-8]' docs/20260725_AI캐릭터관리자웹/plan-task.md— Phase Goal 9개 확인.awk '/^## Phase 0\./,/^## 5\./ { if ($0 ~ /^### Task [0-8]\.[0-9]+ /) n++ } END { print n }' docs/20260725_AI캐릭터관리자웹/plan-task.md— 구현 Task 34개 확인.rg -c '^\*\*Goal 실행P[0-8]-T[0-9]+' docs/20260725_AI캐릭터관리자웹/plan-task.md— Task goal 34개 확인.rg -c '^### Phase [0-8] Gate$'와rg -c '^\*\*Goal 실행P[0-8]-GATE'— Gate와 Gate goal 각각 9개 확인.- PRD
AUDIO-027~028, API Contract §5.0/§5.3, Phase 3 요구사항·contract/form checklist 대조 —themeId와 공통 테마 목록 계약이 일치함을 확인. - 애플리케이션 test/build — 문서와 goal 실행 계약만 변경해 실행하지 않음.
- 남은 항목: Phase 2 이후 구현과 각 도메인의 backend 제공 대기 계약.