docs(ai-character): 관리자 웹 기획 문서 추가
This commit is contained in:
894
docs/20260725_AI캐릭터관리자웹/plan-task.md
Normal file
894
docs/20260725_AI캐릭터관리자웹/plan-task.md
Normal file
@@ -0,0 +1,894 @@
|
||||
# AI 캐릭터 관리자 웹 구현 계획
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to 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.
|
||||
|
||||
| 문서 항목 | 내용 |
|
||||
|---|---|
|
||||
| 상태 | 구현 전 재계획 완료 |
|
||||
| 최초 작성일 | 2026-07-25 |
|
||||
| 재작성일 | 2026-07-26 |
|
||||
| 요구사항 기준 | [prd.md](./prd.md) |
|
||||
| API 기준 | [api-contract.md](./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는 다음 결과를 모두 가진다.
|
||||
|
||||
1. 사용자가 직접 확인할 수 있는 하나 이상의 완결된 흐름
|
||||
2. 해당 Phase가 소유하는 API contract test
|
||||
3. loading·empty·error·success 상태
|
||||
4. 해당 viewport 범위와 keyboard·접근성 검증
|
||||
5. Phase 전용 E2E와 공통 typecheck·lint·build 결과
|
||||
|
||||
각 Task는 다음 Red/Green loop를 따른다.
|
||||
|
||||
1. Task에 적힌 test file에 가장 작은 실패 test를 추가한다.
|
||||
2. 해당 test만 실행해 의도한 assertion 실패인지 확인한다.
|
||||
3. 그 test를 통과시키는 최소 구현을 작성한다.
|
||||
4. 관련 feature test 전체를 실행한다.
|
||||
5. 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를 먼저 보정한다.
|
||||
|
||||
## 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를 막지 않는다.
|
||||
|
||||
```text
|
||||
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. 파일 책임 지도
|
||||
|
||||
```text
|
||||
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 기반을 만든다.
|
||||
|
||||
**주요 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 런타임·패키지 기반
|
||||
|
||||
- [ ] `mise.toml`의 Node `24.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`, `e2e` script를 정의한다.
|
||||
- [ ] 이 Phase에 필요하지 않은 router, server-state, form, drag-and-drop dependency는 아직 설치하지 않는다.
|
||||
|
||||
### Task 0.2 test 기반
|
||||
|
||||
- [ ] 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">`, `main` landmark, root content를 확인하는 unit test를 먼저 실패시킨 뒤 최소 App shell을 만든다.
|
||||
- [ ] 동일 shell이 각 Playwright project에서 열리는 smoke E2E를 만든다.
|
||||
|
||||
### Phase 0 Gate
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
npx playwright install chromium webkit
|
||||
export VITE_API_BASE_URL=http://127.0.0.1:4010
|
||||
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
|
||||
```
|
||||
|
||||
**Expected:** fresh install 후 typecheck·lint·unit·E2E·production build가 모두 0 exit code이며 환경 변수 오류가 test로 고정된다.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1. 공통 플랫폼·인증/인가·컴포넌트 기반
|
||||
|
||||
**목표:** ADMIN 인증 흐름과 보호된 Admin shell을 완결하고, Phase 2 이후 화면이 조합해 사용할 공통 UI·form·file·media component contract를 제공한다.
|
||||
|
||||
**요구사항:** `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,page-header,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 | 공통 책임 |
|
||||
|---|---|---|
|
||||
| `PageHeader`, `PageState` | 1~7 | 제목·action slot, 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에 이미 확정된 반복 소비만 포함한다. 각 도메인 Phase의 component map은 이 배치를 검증하고 domain component 구성을 추가하며, shared component의 존재 근거를 새로 만드는 선행조건이 아니다.
|
||||
|
||||
### Task 1.1 최소 디자인 시스템
|
||||
|
||||
- [ ] PRD `10.9`의 `ui-ux-pro-max` design-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로 고정한다.
|
||||
- [ ] 밝은 `:root` token만 만들고 `.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
|
||||
|
||||
- [ ] `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
|
||||
|
||||
- [ ] 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
|
||||
|
||||
- [ ] 미인증 사용자가 보호 content를 한 프레임도 보지 않고 `/login`으로 이동하는 test를 작성한다.
|
||||
- [ ] 401은 session 제거 후 login, 403은 AccessDeniedPage로 가는 route test를 작성한다.
|
||||
- [ ] desktop sidebar, mobile Sheet, header, logout, breadcrumb, skip link, `main` landmark를 구현한다.
|
||||
- [ ] 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
|
||||
|
||||
- [ ] 위 소비처 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에서 최소 한 번 실제 조합해 integration test를 작성한다.
|
||||
|
||||
### Task 1.6 공통 file·media component
|
||||
|
||||
- [ ] image `10MB`의 정확한 byte 기준을 backend와 맞춰 PRD·API Contract·경계 test에 기록한다. 미확정이면 `FileField`는 주입된 `maxBytes`만 검증하고 도메인 정책 완료를 주장하지 않는다.
|
||||
- [ ] `FileField`는 visible label, 설명·오류 연결, accept 안내, keyboard activation, 선택 취소와 controlled `File | null` contract만 제공한다.
|
||||
- [ ] 공통 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 생성에만 사용한다. 선택 근거는 작업 기록에 남긴다.
|
||||
- [ ] 공통 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
|
||||
|
||||
```bash
|
||||
npm run test:run -- src/shared src/features/auth src/layouts
|
||||
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 실패 경로가 독립적으로 검증된다.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2. Character workspace vertical slice
|
||||
|
||||
**목표:** ADMIN이 active Character를 검색·생성·선택하고 workspace에서 상세·수정·soft delete까지 완료한다.
|
||||
|
||||
**요구사항:** `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-013` contract 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 계약 확인
|
||||
|
||||
- [ ] 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
|
||||
|
||||
- [ ] Character 목록·생성 path에는 `characterId`가 없고 하위 resource route에만 선택한 `characterId`가 들어가는 contract test를 작성한다.
|
||||
- [ ] active-only 목록의 `search`, `page`, `size` URL 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
|
||||
|
||||
- [ ] create multipart가 `request` JSON 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 반응형·접근성
|
||||
|
||||
- [ ] 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
|
||||
|
||||
```bash
|
||||
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를 안전하게 제어한다.
|
||||
|
||||
**요구사항:** `AUDIO-001~026`, `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,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,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 계약 확인
|
||||
|
||||
- [ ] Audio 신규 오류 status/message key와 backend container·codec 오류 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
|
||||
|
||||
- [ ] 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
|
||||
|
||||
- [ ] 생성은 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한다.
|
||||
- [ ] 즉시 공개 기본값은 날짜 입력을 비활성화·초기화하고 `releaseDateUtc=null`을 보낸다.
|
||||
- [ ] 예약 공개는 미래 Asia/Seoul 시각만 받고 UTC ISO-8601 `Z`로 변환하는 test를 작성한다.
|
||||
- [ ] 수정 form은 server `releaseDateUtc/status`로 초기화하고 사용자가 바꾸지 않으면 기존 값을 유지한다.
|
||||
- [ ] 제공된 active Series 목록 endpoint를 사용하는 options request와 `seriesIds` 다중 선택·keyboard 제거를 test한다.
|
||||
- [ ] create payload에 `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 반응형·접근성
|
||||
|
||||
- [ ] 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
|
||||
|
||||
```bash
|
||||
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 전체 순서를 관리한다.
|
||||
|
||||
**요구사항:** `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 계약 확인
|
||||
|
||||
- [ ] 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
|
||||
|
||||
- [ ] 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 연결·해제·전체 순서
|
||||
|
||||
- [ ] 현재 연결 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 반응형·접근성
|
||||
|
||||
- [ ] mobile은 목록·상세·연결 콘텐츠 조회만 제공하고 CRUD·연결·순서 action을 route capability로 차단한다.
|
||||
- [ ] desktop/tablet에서 전체 관리 흐름을 제공한다.
|
||||
- [ ] keyboard-only 요일·genre·연결·정렬, 320px 조회, 200% zoom, axe critical·serious 0건을 확인한다.
|
||||
|
||||
### Phase 4 Gate
|
||||
|
||||
```bash
|
||||
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만으로 게시글 등록·조회·수정·고정·비활성화·첨부 재생을 완료한다.
|
||||
|
||||
**요구사항:** `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 계약 확인
|
||||
|
||||
- [ ] 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
|
||||
|
||||
- [ ] active-only 목록의 `page/size`, loading·empty·error·retry와 URL query 보존을 test한다. 제공 계약에 없는 Community `search` query나 현재 page 한정 client 검색은 만들지 않는다.
|
||||
- [ ] 목록 item을 source로 Sheet를 열고 전용 detail GET을 0회 호출하는 test를 작성한다.
|
||||
- [ ] `/community-posts/:postId`, `/edit` route가 존재하지 않는 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
|
||||
|
||||
- [ ] 생성 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 반응형·접근성
|
||||
|
||||
- [ ] mobile은 목록·Sheet 조회·첨부 재생만 제공하고 등록·수정·고정·비활성화를 route/action policy로 차단한다.
|
||||
- [ ] desktop/tablet에는 전체 관리 흐름을 제공한다.
|
||||
- [ ] Sheet focus trap/복귀, keyboard media/form, 320px overflow, 200% zoom, axe critical·serious 0건을 확인한다.
|
||||
|
||||
### Phase 5 Gate
|
||||
|
||||
```bash
|
||||
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를 최신순·답변 상태로 조회하고 답변을 한 번 작성한 뒤 기존 답변만 수정한다.
|
||||
|
||||
**요구사항:** `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 계약 확인
|
||||
|
||||
- [ ] 목록·상세·답변 수정 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 목록·답변 생성·수정
|
||||
|
||||
- [ ] 기본 목록은 최신순 전체이며 전체/미답변/답변 완료 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 반응형·접근성
|
||||
|
||||
- [ ] desktop/tablet/mobile 모두 조회·답변 작성·수정을 제공한다.
|
||||
- [ ] 320px에서 keyboard가 reply input/submit을 가리지 않는 E2E를 작성한다.
|
||||
- [ ] keyboard-only filter/detail/create/edit, 200% zoom, axe critical·serious 0건을 확인한다.
|
||||
|
||||
### Phase 6 Gate
|
||||
|
||||
```bash
|
||||
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 권한을 일관되게 적용한다.
|
||||
|
||||
**요구사항:** `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 계약 확인
|
||||
|
||||
- [ ] 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
|
||||
|
||||
- [ ] 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
|
||||
|
||||
- [ ] 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 반응형·접근성
|
||||
|
||||
- [ ] 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
|
||||
|
||||
```bash
|
||||
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, 보안, 반응형, 접근성 기준을 만족한다는 최신 증거를 남긴다.
|
||||
|
||||
**주요 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·오류 회귀
|
||||
|
||||
- [ ] 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 반응형·접근성·보안 회귀
|
||||
|
||||
- [ ] 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
|
||||
|
||||
- [ ] 활성 범위의 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
|
||||
|
||||
```bash
|
||||
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~026`, 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~10, §12~14, 활성 범위 전체 | 전체 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. 검증 기록
|
||||
|
||||
구현 단계마다 아래 형식으로 누적하고 기존 기록을 삭제하거나 덮어쓰지 않는다.
|
||||
|
||||
```markdown
|
||||
### 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.md` 1개만 확인, 애플리케이션 코드 변경 0건.
|
||||
- 애플리케이션 test/build — 이번 단계는 계획 전용이고 아직 `package.json`이 없어 실행하지 않음.
|
||||
- 남은 항목: 후속 구현 전체와 각 도메인에 기록한 backend 외부 의존 계약.
|
||||
Reference in New Issue
Block a user