Files
2026-08-05 00:34:04 +09:00

782 KiB
Raw Permalink Blame History

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, TanStack Query, Zod, native history routing, fetch/XHR upload adapter, Vitest, React Testing Library, MSW, Playwright, axe-core.

문서 항목 내용
상태 2026-08-04 P6-R7 Audio preview 시간 domain validation 회귀 수정과 코드-only 재리뷰 완료. P6-R6 가격 domain validation submit 경로 회귀 수정 완료. P6-R5 FileField 이미지 preview·Community 생성 media flow·공용 CAN 가격 계약 구현 완료. P4-R10, P9-R18P9-R19, P10-R16P10-R17 자동 보완 완료, 실제 crop pixel·stale ADMIN·Series/FanTalk/Comments/file policy 수동 QA 대기
최초 작성일 2026-07-25
재작성일 2026-07-26
요구사항 기준 prd.md
API 기준 api-contract.openapi.json
현재 활성 Goal P6-R7 완료

목표

ADMIN이 로그인한 뒤 AI 캐릭터를 선택하고, 선택한 캐릭터 문맥에서 Character·Audio·Series·Community·FanTalk·Comments를 안전하게 관리하는 독립 React SPA를 완성한다.

현재 상태

구분 현재 상태 남은 조건
자동 검증 범위 P4-R10, P9-R18P9-R19, P10-R16P10-R17 current-state contract 자동 보완 완료 실제 crop pixel·stale ADMIN·개발 API 수동 QA
실제 개발 API Series/FanTalk/Comments/file policy 수동 QA 대기 ADMIN credential과 고정 fixture 필요
브라우저·인가 수동 QA 실제 crop pixel 비교와 stale ADMIN server 확인 대기 Chromium/mobile Chrome 환경과 stale ADMIN fixture 필요
문서 구조 P10-R9에서 Goal 실행형 필수 section navigation 복구 기존 Progress·Decision 이력은 보존

범위의 포함·제외

포함

  • AI 캐릭터 관리자 SPA의 인증, Character workspace, Audio, Series, Community, FanTalk, Comments mock/client 구현과 회귀 보완.
  • OpenAPI 2.3.0에서 제공된 v2 lookup, UTC, pagination, FanTalk 수정·삭제, Comments, 가격·오류·파일 정책 반영.
  • mock/client 자동 검증과 실제 개발 API 수동 QA 대기 상태의 분리 기록.

제외

  • FanTalk 별도 상세/filter/sort, 중복 오류 key, 복원·hard delete, refresh token, 자동 mock fallback.
  • 실제 개발 API 수동 QA 완료 주장. credential·fixture가 준비되기 전에는 완료로 표시하지 않는다.
  • WebKit·Mobile Safari는 지원 범위에서 제외한다. E2E는 Chromium/mobile Chrome만 실행한다.

기술적 제약

  • 현재 설치 stack은 package.json 기준 React, TypeScript, Vite, Tailwind CSS, TanStack Query, Zod, Vitest, React Testing Library, MSW, Playwright, axe-core다.
  • routing은 native history 기반이고, transport는 fetch/XHR upload adapter를 사용한다.
  • API 계약은 api-contract.openapi.json을 우선하며 제공되지 않은 endpoint, DTO, enum, 오류 status/key, validation 상한은 추정하지 않는다.
  • JWT와 ADMIN role은 sessionStorage에만 저장하고 민감정보를 console, 분석 이벤트, 영구 저장소에 기록하지 않는다.

하나 이상의 Phase

실행 Phase는 Phase 지도Phase 0부터 Phase 10까지의 본문이 소유한다. 완료된 Phase는 다시 열지 않고 후속 문제는 P<Phase>-R<번호> 회귀 Task로 추적한다.

실행 순서와 의존성

  • 기본 순서는 Phase 0 → 1 → 2 → 3 → 4 → 5/6/7 → 8 → 9 → 10이다.
  • 자동 보완 Task는 완료됐다. 이후 남은 수동 QA와 필요 시 E2E는 Chromium/mobile Chrome만 실행한다.
  • 동시에 하나의 미완료 goal만 운용하고, 완료된 Task의 체크박스와 검증 기록은 되돌리지 않는다.

변경 금지 항목

  • OpenAPI에 없는 계약 추정, mock 성공을 server 완료로 대체, 실제 server QA 완료 허위 표시.
  • 실패 test 삭제·skip·timeout 상향·근거 없는 browser project 제거로 Gate를 통과시키는 변경.
  • 제품 범위 밖 기능 추가, 새 dependency 설치, 과거 Progress·Decision Log 삭제 또는 덮어쓰기.

의사결정 및 중단 규칙

  • 외부 계약이나 권한이 필요한 범위는 담당 주체, 영향, 재개 조건을 기록하고 수동 QA 대기로 유지한다.
  • 같은 차단 사유가 3회 반복되고 문서화 외 진전이 불가능할 때만 blocked로 판정한다.
  • 사용자 직접 지시는 현재 검증 범위에 우선한다. 2026-07-31 이후 지원·자동 검증 browser는 Chromium/mobile Chrome만 사용한다.

Progress

상세 진행 기록은 검증 기록에 누적한다. P4-R10은 가격 기본값 0, native number stepper, 조건부 유료 옵션과 preview duration offset을 자동 검증 완료했다. P9-R18P9-R19, P10-R16P10-R17에서 finding·Task·H2·실제 마지막 Progress contract를 보완해 자동 보완 완료로 판정했다. 실제 crop pixel·stale ADMIN·Series/FanTalk/Comments/file policy 수동 QA는 별도 대기다.

Decision Log

주요 결정은 Phase 운영 규칙, Phase 3~10 OpenAPI 준비 상태, 요구사항 추적표, 검증 기록에 보존한다. 과거 결정은 삭제하지 않고 후속 정정만 누적한다.

발견된 문제

제품·Playwright 설정의 신규 문제는 없다. P4-R10은 2026-08-04에 확정된 Audio form UX 정책을 현재 구현과 정렬했고, 가격 native stepper와 0..99999 검증을 Chromium/mobile Chrome mock E2E로 확인했다. P9-R18, P9-R19, P10-R16, P10-R17로 finding·Task 경계, fenced·중복 H2, 최신 및 중복 Progress marker contract를 보완했다. 실제 crop pixel·stale ADMIN과 개발 API Series/FanTalk/Comments/file policy 수동 QA가 남아 있다.

최종 보고 형식

최종 보고는 한국어로 작성하고, 완료한 Goal ID, 변경 파일, 실행한 검증 명령과 결과, 실행하지 않은 E2E/수동 QA의 사유, 남은 위험을 구분해 요약한다.

1. 전역 제약

  • 계획 보완 단계에서는 PRD·API Contract·plan과 연결 가이드만 동기화하고, 애플리케이션 코드와 프로젝트 설정은 해당 Task goal 실행 때 변경한다.
  • endpoint, query, multipart part, request/response field, required 여부, status와 오류 응답은 api-contract.openapi.json을 우선한다. OpenAPI에 표현되지 않는 제품·UI·운영 정책은 PRD를 따른다.
  • OpenAPI에 없는 기존 인증 POST /admin/member/login, POST /member/logout은 PRD 11.5 EXT-006 현재 구현 기준 계약과 완료된 Phase 1 contract test를 임시 기준으로 유지한다. 정식 계약 제공 전에도 기존 인증을 다시 구현하거나 제거하지 않으며 Phase 3~9 진행을 차단하지 않는다.
  • 로그인은 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 목록은 서버가 isActive=true인 항목만 반환한다. 활성 상태 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을 사용하지 않는다.
  • AI 캐릭터 관리자 API에는 timezone query/body field를 보내지 않는다. 오디오 예약 공개의 Asia/Seoul 입력은 client에서 ISO-8601 UTC Z로 변환하고 즉시 공개는 releaseDate=null을 보낸다.
  • Audio 생성·수정과 Community 생성의 price0..99999 정수만 허용하고 schema·form에서 -1, 100000, 소수를 거부한다.
  • multipart의 JSON part 이름은 request로 고정하고, optional 교체 파일을 보내지 않으면 기존 media를 유지한다.
  • 목록은 operation별 OpenAPI pagination을 사용한다. 공통 page는 기본 0·최소 0, size는 기본 20·최소 1이고 FanTalk size만 20..50으로 보정된다. 검색을 제공하는 목록은 약 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를 먼저 만들고 최소 구현으로 통과시킨다.
  • API mode는 server | mock을 명시적으로 선택한다. 기본 npm run dev는 실제 개발 API, npm run dev:mock은 browser MSW를 사용한다.
  • 실제 API의 404·network error를 감지한 자동 mock fallback은 만들지 않고 production build에서는 mock mode를 거부한다.
  • 제공 계약이 있는 기능만 contract 기반 browser fixture를 만들며, 계약 자체가 없으면 최종 network UI를 추정 구현하지 않는다.
  • 각 도메인은 UI 확인 완료(mock)실제 서버 연동 완료(server)를 별도 증거로 기록하며 mock Gate만으로 Phase 전체 완료를 주장하지 않는다.

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 결과
  6. 제공 계약 범위의 mock preview E2E와 backend 구현 후 server integration 결과를 구분한 기록

각 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.openapi.json의 operation/schema를 contract test로 고정한 뒤 구현한다. 계약 자체의 변경은 backend가 제공한 새 버전을 받은 경우에만 반영한다.
구현됐지만 OpenAPI에서 누락됨 PRD에 현재 endpoint·request/response·검증 근거를 기록하고 기존 회귀 test를 유지한다. 정식 계약 전에는 동작을 확장하지 않지만 독립 Phase 진행은 차단하지 않는다. 현재 해당 항목은 EXT-006 인증뿐이다.
안전한 확정 기본값이 있음 문서에 적힌 최소 규칙만 구현한다. 예: price 상한 미제공 시 0 이상 정수만 검증한다.
계약 없이 안전하게 구현할 수 없음 endpoint·DTO·오류를 추측하지 않는다. 해당 최소 기능 또는 Phase를 현재 릴리스에서 제외하기 전에 PRD 결정 기록, API Contract, 이 계획을 함께 갱신한다.
구현 중 불필요하다고 판단 활성 체크 항목을 제거하되 PRD 결정 기록에 삭제 이유와 날짜를 남긴다. 과거 결정 기록은 지우지 않는다.
계약이 후속 도착 완료한 Phase를 묵시적으로 다시 열지 않고 별도 후속 vertical slice를 계획한다.
  • OQ-009의 결정 절차는 확정됐다. 각 도메인의 초기 UI를 만든 뒤 실제 페이지에서 문자열 입력 공간과 반복 항목 사용성을 검토하고 최대값 권고안을 정한다. backend 호환 확인 후 PRD·OpenAPI 계약·schema·경계 test를 같은 변경에서 갱신하며, 그전에는 임의 상한을 추가하지 않는다.
  • OQ-010 감사 로그 조회 UI는 현재 릴리스 구현 항목을 만들지 않는다. 포함하기로 바뀌면 backend 조회 계약을 포함한 별도 Phase로 다시 계획한다.
  • P0 외부 의존이 남아 있으면 영향을 받는 network flow를 완료로 표시하지 않는다. 다른 독립 Phase는 계속 진행할 수 있다.
  • 이미지 최대 10MB의 정확한 byte 경계처럼 표현만으로 단일 값이 정해지지 않는 항목은 첫 파일 Phase에서 결정 기록과 contract를 먼저 보정한다.

2.4 제공 범위 우선 실행·후속 보완 전략

전체 일정을 미제공 backend 계약 하나에 직렬화하지 않는다. Phase 3부터 각 Phase의 OpenAPI 제공 범위를 먼저 구현하고, 독립적으로 진행 가능한 후속 Phase를 계속 수행해 Phase 9의 활성 릴리스 범위 Gate까지 완료한다.

  1. 각 Phase의 계약 확인 Task에서 제공 operation과 외부 의존을 먼저 분리한다.
  2. 제공된 endpoint·DTO로 안전하게 구현할 수 있는 Task는 mock/server 상태를 구분해 구현·검증한다.
  3. 계약이 없는 기능은 endpoint·DTO·fixture·UI 완료 상태를 추정하지 않고 해당 Task 또는 network 범위만 외부 계약 대기 후속으로 기록한다.
  4. 외부 의존 때문에 실행하지 않은 범위가 있더라도 완료된 독립 Task를 되돌리지 않고 다음 Phase를 진행한다.
  5. 2026-07-28 당시 Phase 8 Comments 계약 부재는 P8-T1P8-GATE 이력으로 보존한다. 2026-07-29 OpenAPI 2.3.0의 댓글 계약 도착 후 구현은 Phase 10에서 새 goal로 진행한다.
  6. Phase 9는 제공 계약과 명시적 제외 범위만 대상으로 실행하고 결과를 활성 범위 완료로 기록한다. 미제공 계약을 포함한 전체 기능 완료로 표현하지 않는다.
  7. backend 계약이 후속 도착하면 완료 Phase를 묵시적으로 다시 열지 않고 별도 vertical slice를 계획해 구현한 뒤 영향받는 회귀와 최종 Gate를 다시 실행한다. OpenAPI 2.3.0 후속 범위는 Phase 10이 소유한다.

이 전략은 추가 사용자 결정을 기다리기 위한 임시 우회가 아니라, 제공 범위의 UI와 공통 품질을 먼저 완성해 전체 lead time을 줄이는 확정 실행 방식이다.

2.5 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 결정 기록 → 제공된 OpenAPI 또는 외부 의존 상태 → 이 계획의 활성 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 개발 전용 Mock Preview 기반 Phase 1 explicit mock/server mode, browser MSW, mock banner, no production fallback
3 Character workspace Phase 2 list/search → create/select → detail/edit → deactivate
4 Audio vertical slice Phase 3의 workspace core list/search/detail/play → create/edit/upload → deactivate
5 Series vertical slice Phase 4의 Audio 조회 API list/detail → 계약 제공 후 CRUD; content link/unlink → full reorder
6 Community vertical slice Phase 4의 media/file primitive list → collection Sheet edit/pin → media play → deactivate
7 FanTalk vertical slice Phase 3 list → reply create/update, fan root delete; 별도 detail/filter/sort는 제품 범위 제외
8 Comments vertical slice Phase 4 + Phase 6 Audio/Community thread → permission별 CRUD
9 교차 회귀·인수인계 활성 범위의 Phase 0~8 전체 journey, viewport, axe, security, mock/server build
10 OpenAPI 2.3.0 후속 계약 vertical slices Phase 9 이력 + OpenAPI 2.3.0 v2 lookup, UTC, pagination, FanTalk 수정·삭제, Comments, 가격·오류·파일 정책, active-only server 검증

기본 진행 순서는 Phase 번호를 따른다. 다만 Phase 5·6·7은 자신의 선행조건과 계약이 충족되면 병행할 수 있고, 외부 계약으로 막힌 Phase가 다른 독립 Phase를 막지 않는다.

3.1 Phase 3~10 OpenAPI 준비 상태

Phase 현재 구현 가능 범위 외부 의존 또는 제외 범위 판정
3 Character 기존 목록·상세·기본 create/update/soft delete mock UI 완료 없음. v2 원작 검색 선택기는 Phase 10에서 구현 완료 EXT-001 해결, legacy 후보 제거 완료
4 Audio 목록·상세·create/update/soft delete mock UI와 UTC releaseDate 전송 완료 status filter와 수정 schedule/theme/file은 계약상 제외 EXT-007 해결, 실제 개발 API 수동 QA 대기
5 Series 목록·상세·연결·해제·전체 순서, v2 활성 장르, CRUD form mock/client 완료 실제 개발 API Series 수동 QA EXT-002 해결, 상세는 SeriesListItem으로 확정
6 Community 목록·Sheet·create/update/pin/soft delete, pagination object, timezone 제거 mock/client 완료 실제 개발 API Community 수동 QA EXT-007~008 해결, active-only 반환 확인 대기
7 FanTalk page 목록, 답변 생성·수정, 팬 원글 삭제 mock/client UI 완료 별도 상세/filter/sort·중복 오류 key는 현재 제품 범위에서 제외 EXT-004 해결, 실제 개발 API 수동 QA만 분리 대기
8 Comments Audio·Community 댓글 2단계 thread와 mock/client UI 완료 실제 개발 API Comments 수동 QA EXT-005 해결, Phase 10 network slice 구현 완료
9 Final 제공 계약과 명시적 제외 범위의 교차 회귀 미제공 P0 범위를 포함한 전체 릴리스 완료 주장 활성 범위 Gate 후 가능
10 Follow-up 원작·장르 lookup, UTC, Community pagination, FanTalk 수정·삭제, Comments, 가격·오류·파일 정책 제품 범위에서 제외한 FanTalk 별도 상세/filter/sort·중복 오류 key와 인증 정식화 현재 확정 범위의 client/mock/server 구현 가능

OQ-009를 포함한 프론트엔드 제품 결정 절차는 확정됐다. 위 표의 대기 사항은 추가 사용자 결정이 아니라 backend OpenAPI 보완 또는 명시적 후속/제외 기록이 필요한 외부 의존이다.

Phase 0 Setup
  └─ Phase 1 Platform + Auth/Authz + Shared Components
       └─ Phase 2 Mock Preview Foundation
            └─ Phase 3 Character Workspace
                 ├─ Phase 4 Audio ──┬─ Phase 5 Series
                 │                  └─ Phase 6 Community ──┐
                 └─ Phase 7 FanTalk                       ├─ Phase 9 Final
                       Phase 4 + Phase 6 ── Phase 8 Comments ┘
                                                               └─ Phase 10 OpenAPI 2.3.0 Follow-up

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의 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 기반

Goal 실행 P0-T2: unit·DOM·E2E test 기반과 접근 가능한 최소 App shell을 완성한다.

  • 시작 조건: P0-T1 완료.

  • 완료 증거: 체크박스 전체, root unit test와 현재 Playwright project smoke 및 검증 기록.

  • 범위 밖: 인증과 실제 도메인 route.

  • Vitest, jsdom, React Testing Library, jest-dom을 구성한다.

  • 각 test 뒤 DOM·mock·storage가 정리되는 공통 setup을 만든다.

  • Playwright에 desktop Chromium과 mobile Chrome viewport project, Vite webServer를 구성한다.

  • fresh environment에서 Chromium browser binary를 설치하는 명령을 README와 Gate에 포함한다.

  • <html lang="ko">, main landmark, 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
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 증거 정합성을 복구하고 회귀를 방지한다.

  • 연결 리뷰: Phase 0·1 리뷰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.tsvi.stubGlobal 누출을 실패 재현한 뒤 restoreAllMocks·unstubAllGlobals 정리를 추가했다.

  • src/app/App.test.tsxtests/e2e/smoke.spec.ts에서 root main 안의 로그인 대표 콘텐츠를 검증한다.

  • 존재하지 않는 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 cinode_modules를 재설치하는 동안 동시에 실행한 tsc/playwright 명령이 bin 삭제로 실패했다. npm ci 완료 후 같은 명령을 재실행해 모두 성공했다.

Task R0.2 — API origin 문서·E2E 단일 source 복구

Goal 실행 P0-R2: 현재 .env API origin을 실행 문서와 Playwright route/probe가 함께 사용하게 해 mock/server Gate의 host drift를 제거한다.

  • 연결 리뷰: Phase 0 현재 리뷰REV-P0-004

  • 시작 조건: .env.development=https://test-api.sodalive.net, .env.production=https://api.sodalive.net인 현재 working tree와 VITE_API_BASE_URL runtime 검증.

  • 완료 증거: stale origin에서 실패하는 mock/server E2E RED, origin 단일 source 적용, focused/full mock·server E2E와 문서/diff 검증 기록.

  • Files: Modify: README.md, docs/agent-guide/environment.md, tests/e2e/auth.spec.ts, tests/e2e/accessibility-shell.spec.ts, tests/e2e/server-mode-boundary.spec.ts, tests/e2e/error-mapping.spec.ts, tests/e2e/comments-test-support.ts, 필요 시 Playwright 전용 env helper/config.

  • Interfaces: 앱, browser mock handler, Playwright route/probe는 같은 개발 API base URL에서 파생하고 개발/프로덕션 host를 여러 파일에 상수로 복제하지 않는다.

  • 범위 밖: .env를 과거 host로 되돌리기, unhandled request를 bypass/warn으로 완화하기, 실제 ADMIN credential 발급.

  • RED: 현재 .env.development에서 과거 test-character-admin route/probe가 매칭되지 않아 mock error-mapping과 server auth/boundary E2E가 실패함을 기록했다.

  • GREEN: Playwright가 현재 개발 API origin을 한 source에서 읽도록 수정하고 README·환경 가이드의 현재값을 정렬했다.

  • REFACTOR: 과거 host literal 검색을 0건으로 만들고 focused Comments server route와 full mock/server Gate를 재실행했다.

  • 실행 명령: npm run e2e:mock -- tests/e2e/error-mapping.spec.ts; npm run e2e:mock; npm run e2e; npm run e2e -- tests/e2e/comments.spec.ts --project=chromium; rg -n 'test-character-admin|character-admin\\.sodalive\\.net' README.md docs/agent-guide tests/e2e; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 자동 명령 exit 0, Playwright unhandled request 0건, 현재 개발/프로덕션 origin 문서 불일치 0건.

  • 수동 확인: mock mode는 현재 개발 origin을 intercept하고 server mode는 browser MSW 없이 같은 origin으로 요청하는지 network에서 확인한다.

P0-R2 수정 검증 기록 (2026-07-30):

  • RED: npm run e2e:mock -- tests/e2e/error-mapping.spec.ts — 4 failed / 4 passed. 과거 test-character-admin 요청이 MSW onUnhandledRequest: "error"에 걸려 기대 401 대신 500을 반환했다. npm run e2e -- tests/e2e/server-mode-boundary.spec.ts --project=chromium — 2 failed / 2 passed. stale route가 현재 .env.development origin 요청을 intercept하지 못해 404 한국어 message와 network retry assertion이 실패했다.
  • GREEN: tests/e2e/api-base-url.ts를 추가해 E2E route/probe가 .env.developmentVITE_API_BASE_URL을 읽게 하고, README.mddocs/agent-guide/environment.md를 현재 개발/프로덕션 origin으로 정렬했다. npm run e2e:mock -- tests/e2e/error-mapping.spec.ts — 8 passed. npm run e2e -- tests/e2e/server-mode-boundary.spec.ts --project=chromium — 4 passed.
  • REFACTOR/회귀: rg -n 'test-character-admin|character-admin\.sodalive\.net' README.md docs/agent-guide tests/e2e — no matches. npm run e2e -- tests/e2e/comments.spec.ts --project=chromium — 3 passed. npm run e2e — 36 passed. npm run e2e:mock 단일 실행은 900초 제한으로 188/228 진행 중 timeout됐고, 같은 allowlist를 project별로 분할해 chromium 57 passed, webkit 49 passed / 8 skipped, mobile-chrome 52 passed / 5 skipped, mobile-safari 48 passed / 9 skipped를 확인했다.
  • Type/LSP/build: npm run typecheck, npm run lint, npm run build — 모두 exit 0. LSP diagnostics: tests/e2e 18 files / 오류 0건, 변경 E2E 파일 개별 오류 0건.
  • 수동/표면 확인: mock error-mapping은 현재 개발 origin을 MSW handler가 intercept해 한국어 401/403/415를 반환했고, server mode는 browser MSW 없이 같은 origin route/probe에서 404·network retry와 Comments server route가 통과했다. timeout으로 남은 Vite mock server PID는 종료해 포트 8888/8889 listen 0건을 확인했다.

Task R0.3 — 운영체제 메타데이터의 저장소 유입 차단

Goal 실행 P0-R3: 문서 디렉터리에 index된 .DS_Store를 제거하고 같은 운영체제 메타데이터가 다시 version control 대상이 되지 않게 한다.

  • 연결 리뷰: Phase 0 현재 리뷰REV-P0-005

  • 시작 조건: git ls-files 'docs/20260725_AI캐릭터관리자웹/.DS_Store'가 index entry를 반환하고 .gitignore.DS_Store 규칙이 없는 현재 working tree.

  • 완료 증거: 해당 index entry 삭제, 저장소 전역 ignore 규칙, 추적 .DS_Store 0건과 targeted diff 검증 기록.

  • Files: Modify: .gitignore; Delete: docs/20260725_AI캐릭터관리자웹/.DS_Store; Test: 없음 — runtime 동작이 없는 repository hygiene 변경이다.

  • Interfaces: 애플리케이션 runtime, package script, 문서 내용에는 영향을 주지 않는다.

  • 범위 밖: 다른 사용자 산출물 삭제, .gitignore 전면 정리, 애플리케이션 코드·dependency 변경.

  • P0-GATE 연결: runtime Gate 재실행 대신 repository 정적 검증을 수행하고 git diff --check가 통과하기 전에는 Task를 완료하지 않는다.

  • TDD 예외 사유: 운영체제 metadata의 index 포함 여부와 ignore 규칙은 runtime 동작이 없어 unit test로 실패를 만드는 것보다 Git index 정적 검사가 직접적인 증거다.

  • 대체 검증 방법: cleanup 전후 git ls-files.gitignore 검색을 비교하고 targeted git diff --check 결과를 기록한다.

  • RED 대체: git ls-files --stage | rg '\.DS_Store'git status --short --untracked-files=all | rg '\.DS_Store'로 현재 staged entry를 재현했다.

  • .DS_Store를 index와 working tree에서 제거하고 저장소 전역 ignore 규칙을 한 줄 추가했다.

  • 추적 entry 0건, ignore 적용, whitespace 오류 0건을 확인하고 Progress에 실제 결과를 기록했다.

  • 실행 명령: git ls-files | rg '(^|/)\.DS_Store$'; rg -n '^\.DS_Store$' .gitignore; git diff --check -- .gitignore docs/20260725_AI캐릭터관리자웹.

  • 기대 결과: 첫 명령은 no matches, ignore 검색은 정확히 한 규칙을 반환하고 diff 검증은 exit 0.

  • 수동 확인: 없음. binary 운영체제 메타데이터 제거는 index와 ignore 정적 결과로 판정한다.

P0-R3 수정 검증 기록 (2026-07-31):

  • RED 대체: git ls-files --stage | rg '\.DS_Store'git status --short --untracked-files=all | rg '\.DS_Store'에서 docs/20260725_AI캐릭터관리자웹/.DS_Store staged entry를 확인했다. rg -n '^\.DS_Store$' .gitignore는 no matches였다.
  • GREEN: .gitignore.DS_Store 규칙을 추가하고 git rm --cached -- docs/20260725_AI캐릭터관리자웹/.DS_Store와 working tree 파일 삭제로 운영체제 metadata를 제거했다.
  • 검증: git ls-files | rg '(^|/)\.DS_Store$' — no matches. git status --short --untracked-files=all | rg '\.DS_Store' — no matches. rg -n '^\.DS_Store$' .gitignore7:.DS_Store. git diff --check -- .gitignore docs/20260725_AI캐릭터관리자웹 — exit 0.
  • LSP: .gitignore는 extension 없는 ignore 파일이라 설정된 LSP 서버가 없어 diagnostics 대상이 아니다. runtime 동작 없는 repository hygiene 변경이므로 정적 Git 검증으로 판정했다.

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 완료, PRD 10.1~10.9 확인.

  • 완료 증거: 체크박스 전체, token/component test, UX 검색 채택·제외 기록, typecheck·lint·build.

  • 범위 밖: dark mode, 전체 primitive 선행 scaffold, 도메인 전용 UI.

  • PRD 10.9ui-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

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.tokendata.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 3 Character 데이터와 mutation.

  • 미인증 사용자가 보호 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 3이 교체할 명시적 빈 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에서 실제 소비되는 PageState integration 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, 선택 취소와 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 생성에만 사용한다. 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 계약으로 확정한다.

  • 연결 리뷰: Phase 0·1 리뷰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 단일 요청 계약을 복구한다.

  • 연결 리뷰: Phase 0·1 리뷰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 3 권한 기능.

  • 지연 403 전 보호 shell 비노출과 probe size=20 request assertion을 src/app/App.test.tsx에 추가했다.

  • 로그인 실패 시 Error.message의 서버 한국어 message를 우선 표시하도록 LoginPage를 수정했다.

  • AuthSessionProvider.logout에 in-flight promise coalescing을 추가해 연속 입력도 /member/logout 1회만 보내게 했다.

Task R1.4 — crop·음성 파일 정책 회귀 수정

Goal 실행 P1-R4: Phase 1 파일·미디어 입력의 crop 결과와 audio 형식 조합 계약을 복구한다.

  • 연결 리뷰: Phase 0·1 리뷰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를 추가했다.

  • ImageCropDialogaspect: "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 경계를 복구한다.

  • 연결 리뷰: Phase 0·1 리뷰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을 공유했다.

  • FileField error 상태 input에 aria-invalid="true"를 연결했다.

  • AdminAudioPlayer group 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.tscreateCroppedImageFile keeps a wide no-upscale crop opaque at the top and bottom test로 보강했다.
  • 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.tspolicy.maxBytes 미구현으로 실패 확인. 이후 IMAGE_MAX_BYTES export 누락으로 실패 확인.
  • 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 typechecknpm run build 성공으로 보완 확인했다.

Task R1.1 — Phase 1 산출물·통합·Gate 완료 증거 복구

Goal 실행 P1-R1: Phase 1 완료 판정을 재현 가능한 산출물·통합·Gate 증거로 복구한다.

  • 연결 리뷰: Phase 0·1 리뷰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건으로 정리했다.

Task R1.6 — 모든 보호 요청 401의 인증 만료 처리

Goal 실행 P1-R6: 응답 body 형식과 무관하게 보호 요청의 HTTP 401에서 session을 한 번 제거하고 login으로 이동한다.

  • 연결 리뷰: Phase 1 재리뷰REV-P1-011

  • 시작 조건: 기존 concurrent 401 burst 규칙과 UNKNOWN_API_ERROR_MESSAGE를 보존한다.

  • Files: Modify: src/shared/api/client.ts, src/shared/api/__tests__/client-auth.test.ts, src/shared/api/__tests__/client.test.ts; Test: 동일 경로.

  • Interfaces: createApiClient({clearSession,onAuthExpired}); malformed JSON/envelope·empty body 401도 ApiError.status=401을 유지한다.

  • 범위 밖: refresh token, 403 session 제거, XHR upload adapter(P4-R2).

  • RED: malformed JSON, empty body, schema 불일치 401 각각에서 clearSession/onAuthExpired가 한 burst에 1회 호출되는 실패 test를 추가했다.

  • GREEN: HTTP status를 기준으로 인증 만료를 먼저 처리하되 기존 오류 정규화와 concurrent request coalescing을 유지했다.

  • REFACTOR: 정상 오류 envelope 401과 malformed 401의 중복 분기를 status 기반 단일 처리로 정리하고 공통 API 회귀를 실행했다.

  • 실행 명령: npm run test:run -- src/shared/api/__tests__/client-auth.test.ts src/shared/api/__tests__/client.test.ts; npm run test:run -- src/features/auth src/app.

  • 기대 결과: exit 0, 모든 보호 401에서 burst별 clear/redirect 1회, 403 clear 0회.

  • 수동 확인: session이 있는 보호 route에서 empty-body 401을 반환했을 때 login 이동과 만료 안내를 확인한다.

P1-R6 수정 검증 기록 (2026-07-30):

  • RED: npm run test:run -- src/shared/api/__tests__/client-auth.test.ts src/shared/api/__tests__/client.test.ts — malformed JSON, empty body, schema mismatch 401에서 clearSession 0회로 3 tests가 기대대로 실패했다.
  • GREEN focused: 같은 command — 성공, 2 files / 21 tests passed. 보호 401의 body 형식과 무관하게 burst별 clearSession/onAuthExpired 1회를 확인했고 403 clear 0회 회귀를 유지했다.
  • LSP diagnostics: src/shared/api/client.ts, src/shared/api/__tests__/client-auth.test.ts — 오류 0건.
  • Auth/session focused regression: npm run test:run -- src/features/auth/tests/auth-session.test.tsx src/features/auth/tests/login-page.test.tsx src/app/App.test.tsx src/app/App.protected-auth.test.tsx — 성공, 3 files / 24 tests passed.
  • 참고: 계획상 broad regression npm run test:run -- src/features/auth src/app은 6 failed / 47 passed로 종료했다. 실패 6건은 REV-P10-001/P10-R1의 공통 오류 fallback stale assertion 범위(API 응답 형식이 올바르지 않습니다. vs 알 수 없는 오류가 발생했습니다.)와 일치해 P1-R6 완료 판정에서 제외하고 기존 후속 Task로 유지한다.

Task R1.7 — zoom no-upscale와 touch pinch crop 복구

Goal 실행 P1-R7: 현재 zoom의 선택 source pixel을 넘지 않는 crop 결과와 pointer pinch 입력을 제공한다.

  • 연결 리뷰: Phase 1 재리뷰REV-P1-012, REV-P1-013

  • 시작 조건: FILE-012, PRD 10.4, 기존 keyboard/range/reset contract 유지.

  • Files: Modify: src/shared/lib/crop-image.ts, src/shared/lib/crop-image.test.ts, src/shared/ui/image-crop-dialog.tsx, src/shared/ui/__tests__/image-crop-dialog.test.tsx; Test: 동일 경로.

  • Interfaces: output size는 현재 CropSourceRect.width/height와 resource maxWidth 중 작은 값; pinch는 두 pointer 거리 변화로 기존 zoom 1..3을 갱신한다.

  • 범위 밖: resource별 aspect/maxWidth 변경, GIF crop, 외부 gesture dependency.

  • RED: zoom 1.5에서 600px source crop이 400px 이하 output을 생성하는 pixel test와 두 pointer pinch가 zoom을 바꾸는 Dialog test를 실패시킨다.

  • GREEN: output size에 현재 zoom을 전달하고 native pointer map으로 최소 pinch를 구현한다.

  • REFACTOR: drag와 pinch pointer cleanup을 정리하고 Character/Audio/Series/Community crop 회귀를 실행한다.

  • 실행 명령: npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx src/features/characters src/features/audio-contents src/features/series src/features/community-posts.

  • 기대 결과: exit 0, noUpscale=true의 output/source scale <=1, 기존 keyboard/range test와 resource aspect test 통과.

  • 수동 확인: tablet touch에서 drag·pinch·range·keyboard 대안과 예상 결과 pixel 표시를 확인한다.

P1-R7 수정 검증 기록 (2026-07-30):

  • RED: npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx — 2 failed / 9 passed. calculateCropOutputSize는 zoom 1.5에서도 600×600을 반환했고, Dialog two-pointer pinch는 zoom을 변경하지 않아 예상 결과 400×400을 찾지 못했다.
  • GREEN focused: 같은 command — 성공, 2 files / 14 tests passed. noUpscale=true에서 zoom 1.5의 output 400×400, 210:297 zoom 1.8의 source 이하 output 236×333, tiny source 1×1, aspect 2 tiny source 1×1, free aspect tiny source 1×1, range/keyboard output 재계산, two-pointer pinch zoom 1.5 apply payload와 touch-action: none을 확인했다.
  • Broad crop regression: npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx src/features/characters src/features/audio-contents src/features/series src/features/community-posts — 성공, 23 files / 119 tests passed.
  • Surface E2E: npm run e2e:mock -- tests/e2e/series.spec.ts --project=chromium — 성공, 7 tests passed. Series 생성 흐름에서 실제 브라우저의 이미지 crop Dialog open/apply 경로를 확인했다.
  • Type/LSP: npm run typecheck, npm run lint — 성공(exit 0). lsp_diagnostics src/shared/ui, tests/e2e — 오류 0건. src/shared/lib/crop-image.ts, src/shared/lib/crop-image.test.ts 단일 fresh diagnostics는 3초 timeout이었고 typecheck/lint로 보완 확인했다.
  • Reviewer blocker 보강: 1차 P1-R7 review는 210:297 zoom 1.8에서 output 236×334가 source 236×333을 1px 초과하고, crop preview에 touch-action: none이 없어 실제 touch pinch가 browser 기본 gesture에 취소될 수 있다고 BLOCKED 판정했다. 추가 RED npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx는 2 failed / 11 passed였고, tiny source 0×0 붕괴 RED npm run test:run -- src/shared/lib/crop-image.test.ts는 1 failed / 6 passed였다. 2차 review는 aspect 2 tiny source가 1×0으로 붕괴한다고 BLOCKED 판정했고 추가 RED npm run test:run -- src/shared/lib/crop-image.test.ts는 1 failed / 6 passed였다. 3차 review는 free aspect tiny source가 1×2로 source height를 초과한다고 BLOCKED 판정했고 추가 RED npm run test:run -- src/shared/lib/crop-image.test.ts는 2 failed / 5 passed였다. 수정 후 위 focused/broad/E2E/type/lint/diff 검증이 통과했다.
  • 수동 확인 한계: 실제 tablet touch 장비 QA는 이 작업 환경에서 수행하지 못했다. native pointer event 회귀 test로 drag·pinch·range·keyboard 대안을 자동 검증했으며, 실기기 touch 확인은 릴리스 QA 항목으로 유지한다.

Task R1.8 — 사용되지 않는 공통 pagination helper 계약 정리

Goal 실행 P1-R8: 사용되지 않는 공통 pagination helper의 FanTalk 전용 20..50 size clamp와 page 최소값 누락을 제거해 공통 계약을 오해할 여지를 없앤다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-014

  • 시작 조건: PRD §11.1의 공통 page >= 0, size >= 1, 전역 최대값 없음과 FanTalk만 size=20..50인 예외 계약.

  • 완료 증거: production 사용처 검색, stale helper 제거, 도메인 adapter pagination 회귀와 전체 정적 Gate 통과.

  • Files: Modify: src/shared/api/pagination.ts, src/shared/api/__tests__/pagination.test.ts; Test: 각 도메인 contract test.

  • Interfaces: 실제 소비 중인 PageData type은 유지한다. production 소비자가 없는 PageParams, createPageParams와 잘못된 clamp test를 제거하고 도메인별 adapter의 계약별 normalization은 보존한다.

  • 범위 밖: 모든 도메인 pagination의 새 공통 abstraction, FanTalk의 명시적 20..50 보정 변경, 화면 page-size 선택지 변경.

  • RED 대체: rg -n 'createPageParams' src로 production 사용처 0건과 test-only 사용을 기록하고, 현재 helper가 {page:-1,size:1}{size:51}을 공통 계약과 다르게 처리함을 고정했다.

  • GREEN: 사용되지 않는 PageParams, createPageParams와 그 동작 test를 삭제하고 실제 소비 중인 PageData type만 유지했다.

  • REFACTOR: 새 helper를 만들지 않고 Character/Audio/Series/Community/Comments/FanTalk adapter의 기존 contract test로 각 pagination 예외를 확인했다.

  • 실행 명령: rg -n 'createPageParams' src; npm run test:run -- src/shared/api src/features/characters src/features/audio-contents src/features/series src/features/community-posts src/features/comments src/features/fan-talks; npm run typecheck; npm run lint.

  • 기대 결과: production createPageParams 사용 0건, 공통 최대 50 오해를 고정하는 test 0건, 모든 명령 exit 0.

  • 수동 확인: 없음. 사용되지 않는 내부 helper 제거 Task이므로 contract test와 정적 검색으로 대체한다.

P1-R8 수정 검증 기록 (2026-07-30):

  • RED 대체: rg -n 'createPageParams|PageParams|PageData|normalizeSize' src에서 createPageParams production 사용처 0건, src/shared/api/__tests__/pagination.test.ts test-only 사용과 src/shared/api/pagination.ts 정의만 확인했다. 기존 npm run test:run -- src/shared/api/__tests__/pagination.test.ts는 1 file / 5 tests passed로 잘못된 공통 size 20..50 clamp test가 통과함을 확인했다.
  • GREEN: src/shared/api/pagination.ts에서 PageParamscreatePageParams를 삭제하고 PageData만 유지했다. src/shared/api/__tests__/pagination.test.tsPageData shape test만 남겼다.
  • REFACTOR/회귀: rg -n 'createPageParams|type PageParams|import .*PageParams' src — no matches. npm run test:run -- src/shared/api src/features/characters src/features/audio-contents src/features/series src/features/community-posts src/features/comments src/features/fan-talks — 35 files / 201 tests passed. npm run typecheck, npm run lint — 모두 exit 0.
  • LSP diagnostics: 변경 파일 단일 fresh diagnostics는 3초 제한으로 timeout됐고, npm run typechecknpm run lint 성공으로 보완 확인했다.

Task R1.9 — 공통 pagination control ID 격리

Goal 실행 P1-R9: 한 화면에 둘 이상의 ResourcePagination이 렌더링돼도 각 페이지 크기 label이 자신의 select와 유일하게 연결되도록 한다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-015

  • 시작 조건: PRD §10.7의 visible label·연결 가능한 접근성 이름과 Comments의 root/reply pagination 동시 렌더링 구조.

  • 완료 증거: 두 인스턴스의 중복 ID를 재현하는 실패 test, React useId 기반 최소 수정, shared UI·Comments focused unit과 mock E2E·정적 Gate 통과.

  • Files: Modify: src/shared/ui/resource-pagination.tsx, src/shared/ui/__tests__/resource-pagination.test.tsx; Test: 필요 시 src/features/comments/tests/comment-thread.test.tsx, tests/e2e/comments.spec.ts.

  • Interfaces: 페이지·크기 변경 callback과 URL/API pagination 값은 바꾸지 않고 인스턴스별 select ID와 htmlFor만 유일하게 만든다.

  • 범위 밖: 새 pagination abstraction, page-size option 변경, 도메인별 pagination query 정책 변경.

  • RED: 한 container에 ResourcePagination 두 개를 렌더링해 select ID 중복과 두 label이 같은 control을 가리키는 실패 test를 작성했다.

  • GREEN: 컴포넌트 인스턴스별 안정적인 ID를 만들고 각 label의 htmlFor를 해당 select에 연결했다.

  • REFACTOR: 공개 prop는 늘리지 않고 기존 component 내부에서 ID 생성을 끝내며 단일 인스턴스 동작을 보존했다.

  • 실행 명령: npm run test:run -- src/shared/ui/__tests__/resource-pagination.test.tsx src/features/comments; npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 동시 렌더링된 pagination select ID 중복 0건, 각 visible label과 control 연결 일치.

  • 수동 확인: Community Sheet에서 root와 reply pagination이 함께 보이는 fixture로 각 페이지 크기 label을 클릭해 대응 select가 focus되는지 확인한다.

P1-R9 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/ui/__tests__/resource-pagination.test.tsx — 1 failed / 2 passed. 두 ResourcePagination의 label control이 같은 id="resource-page-size" 해석에 의존해 첫 번째 select를 가리키는 실패를 확인했다.
  • GREEN focused: npm run test:run -- src/shared/ui/__tests__/resource-pagination.test.tsx src/features/comments — 성공, 3 files / 14 tests passed. React useId 기반으로 select ID 중복 0건과 각 label/control 연결 일치를 확인했다.
  • 개발 중 E2E: 사용자 지시에 따라 전체 Task 완료 전 반복 실행하지 않았다. 지정 mock Comments E2E는 최종 회귀 단계에서 필요 시 실행한다.

Task R1.10 — crop frame과 저장 source 좌표 일치 복구

Goal 실행 P1-R10: ImageCropDialog에서 보이는 crop frame·이동·확대 결과와 canvas가 저장하는 원본 source rectangle을 같은 좌표계로 계산한다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-016

  • 시작 조건: PRD FILE-008, Image crop UI 흐름 4, 현재 ImageCropDialog의 CSS px offset과 calculateCropSourceRect의 원본 px offset 직접 공유.

  • 완료 증거: 축소 표시된 대형 원본과 고정/free aspect 각각의 미리보기-저장 좌표 실패 test, 단일 좌표 변환 최소 구현, shared crop unit과 Character·Audio·Series·Community focused 회귀 및 mock Chromium crop 흐름 통과.

  • Files: Modify: src/shared/ui/image-crop-dialog.tsx, src/shared/lib/crop-image.ts, src/shared/ui/__tests__/image-crop-dialog.test.tsx, src/shared/lib/crop-image.test.ts; Test: src/features/characters/tests/CharacterCreatePage.test.tsx, src/features/audio-contents/tests/audio-form.test.tsx, src/features/series/tests/series-form.test.tsx, src/features/community-posts/tests/community-form.test.tsx, tests/e2e/character-workspace.spec.ts, tests/e2e/audio-content.spec.ts, tests/e2e/series.spec.ts, tests/e2e/community.spec.ts.

  • Interfaces: CropRenderRequest의 offset 의미를 표시 frame 기준으로 명확히 하고, preview transform과 calculateCropSourceRect가 동일한 scale·clamp를 사용한다. resource별 aspect와 최대 출력 크기는 바꾸지 않는다.

  • 범위 밖: 새 crop library/dependency, 회전·필터·다중 crop, 서버 파일 계약 변경.

  • Phase 1 Gate 연결: shared crop focused unit, 네 도메인 crop 회귀, mock Chromium crop journey, typecheck·lint·build가 모두 통과하기 전에는 종결하지 않는다.

  • RED: 4,000×3,000 원본이 341×256px로 표시될 때 10 CSS px 이동이 원본 scale에 맞게 환산되고, 1:1·210:297 frame 밖 영역이 결과에 포함되지 않아야 하는 실패 test를 작성했다.

  • GREEN: 실제 preview frame 크기와 표시 image scale을 기준으로 source offset을 계산하고, 고정/free aspect frame과 canvas source rectangle을 일치시켰다.

  • REFACTOR: 좌표 계산을 shared crop 함수 한 곳에 유지하고 도메인별 보정값이나 새 상태 library를 만들지 않은 채 관련 회귀를 실행했다.

  • 실행 명령: npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx; npm run test:run -- src/features/characters src/features/audio-contents src/features/series src/features/community-posts; npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/series.spec.ts tests/e2e/community.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, preview frame과 저장 결과의 source rectangle 일치, resource별 aspect·no-upscale 회귀 0건.

  • 수동 확인: 4:3 대형 image를 Character/Audio의 1:1, Series의 210:297, Community의 free crop에서 이동·확대하고 적용 전 frame과 적용 후 preview의 같은 영역을 비교한다.

P1-R10 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx — 2 failed / 14 passed. 4,000×3,000 원본의 1:1 preview frame에서 10 CSS px 이동이 sourceX 490으로 계산됐고, Dialog의 renderCrop request에는 previewFrameWidth/Height가 없어 기대대로 실패했다.
  • GREEN: CropRenderRequestcalculateCropSourceRect가 적용 시점의 preview frame 크기를 optional로 받아 CSS px offset을 source px로 환산하게 했다. ImageCropDialog는 preview image의 getBoundingClientRect()를 request에 전달한다.
  • Focused: 같은 command — 2 files / 16 tests passed. 1:1과 210:297 frame source rect 환산, 기존 zoom/no-upscale/keyboard/pinch/free ratio 회귀를 확인했다.
  • 도메인 회귀: npm run test:run -- src/features/characters — 6 files / 43 tests passed. npm run test:run -- src/features/audio-contents — 8 files / 55 tests passed. npm run test:run -- src/features/series — 9 files / 38 tests passed. npm run test:run -- src/features/community-posts는 180초 timeout이 아닌 1 failed / 43 passed로 community-sheet.test.tsx 목록 로딩 실패가 재현됐으나, 같은 spec 단독은 1 file / 7 tests passed, community-form.test.tsx 단독은 1 file / 10 tests passed로 crop 변경 실패와 분리했다.
  • LSP diagnostics: src/shared/lib/crop-image.ts, src/shared/ui/image-crop-dialog.tsx, src/shared/ui/__tests__/image-crop-dialog.test.tsx, src/shared/lib, src/shared/ui — 오류 0건. src/shared/lib/crop-image.test.ts 단일 diagnostics는 timeout됐고 directory diagnostics로 보완했다.
  • E2E/수동 확인: 사용자 지시에 따라 개발 중 반복 E2E는 실행하지 않고 최종 회귀 단계에서 필요 시 mock Chromium crop 흐름을 실행한다.

Task R1.11 — crop 적용 비동기·Blob URL 수명 복구

Goal 실행 P1-R11: crop 적용을 single-flight로 처리하고 실패를 복구 가능하게 표시하며 생성한 Blob URL을 적용·취소·교체·unmount에서 정확히 한 번 해제한다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-017

  • 시작 조건: P1-R10 완료, 현재 다섯 crop source 생성 경로가 성공한 URL.createObjectURL을 해제하지 않고 ImageCropDialog.applyCrop이 pending/error 상태를 갖지 않는 구조.

  • 완료 증거: 적용 연타·render reject·URL 교체/취소/unmount 실패 test, 최소 pending/error/cleanup contract, shared와 네 소비 도메인 회귀 및 정적 Gate 통과.

  • Files: Create: src/shared/lib/create-image-crop-source.ts, src/shared/lib/create-image-crop-source.test.ts; Modify: src/shared/ui/image-crop-dialog.tsx, src/shared/ui/__tests__/image-crop-dialog.test.tsx, src/features/characters/pages/CharacterCreatePage.tsx, src/features/characters/pages/CharacterEditPage.tsx, src/features/characters/tests/CharacterCreatePage.test.tsx, src/features/characters/tests/CharacterEditPage.test.tsx, src/features/audio-contents/pages/AudioContentFormPage.tsx, src/features/audio-contents/tests/audio-form.test.tsx, src/features/series/pages/SeriesFormPage.tsx, src/features/series/tests/series-form-crop.test.tsx, src/features/community-posts/lib/create-community-post-crop-source.ts, src/features/community-posts/tests/community-form.test.tsx.

  • Interfaces: crop source는 자신이 만든 Blob URL의 idempotent release 책임을 노출한다. 적용 pending 동안 적용·취소·이동 control의 재진입을 막고 role=status를 표시하며 reject 시 dialog와 원본 선택을 유지해 재시도할 수 있게 한다.

  • 범위 밖: upload 진행률, File 자체 폐기, 범용 async hook, 외부 image editor dependency.

  • Phase 1 Gate 연결: shared lifecycle unit과 각 도메인 cancel/stale/reject 회귀, typecheck·lint·build가 통과하기 전에는 완료하지 않는다.

  • RED: 적용 연타 시 renderer 1회, reject 후 visible alert·재시도, 적용·취소·새 선택·unmount마다 소유 Blob URL revokeObjectURL 정확히 1회를 기대하는 실패 test를 작성한다.

  • GREEN: 공통 crop source의 idempotent release와 dialog single-flight/error 상태를 최소 구현하고 각 소비 화면이 수명 종료 시 release한다.

  • REFACTOR: 중복된 다섯 createObjectURL helper를 공통 함수로만 합치고 도메인 form state나 upload contract는 바꾸지 않는다.

  • 실행 명령: npm run test:run -- src/shared/lib/create-image-crop-source.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx; npm run test:run -- src/features/characters src/features/audio-contents src/features/series src/features/community-posts; npm run typecheck; npm run lint; npm run build; git diff --check.

  • 기대 결과: 모든 명령 exit 0, pending 중 renderer 최대 1회, reject 후 입력 보존·재시도 가능, 소유 Blob URL 누수·중복 revoke 0건.

  • 수동 확인: 10MB에 가까운 image를 연속 선택·취소·적용하고 느린 crop renderer 조건에서 진행 안내, control 비활성화, 실패 후 재시도를 확인한다.

P1-R11 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/lib/create-image-crop-source.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx는 구현 전 create-image-crop-source import 부재와 ImageCropDialog pending/error/single-flight 부재로 실패했다.
  • GREEN: createImageCropSource가 소유 Blob URL의 idempotent release를 노출하고 load error에서 즉시 revoke한다. ImageCropDialog는 적용 중 role=status, control disabled, renderer reject role=alert, 재시도를 제공한다.
  • REFACTOR: Character create/edit, Audio, Series, Community create/sheet의 중복 createObjectURL helper를 공통 helper로만 합쳤고, 각 crop source 교체·취소·적용·unmount와 stale selection에서 release를 호출한다. 도메인 form state와 upload contract는 변경하지 않았다.
  • Focused 검증: npm run test:run -- src/shared/lib/create-image-crop-source.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx — 2 files / 11 tests passed. 인접 도메인 단독 검증은 Character create/edit, Audio form 3종, Series form — 6 files / 57 tests passed, Community form — 1 file / 10 tests passed, Community sheet 단독 — 1 file / 7 tests passed였다.
  • 정적 Gate: src/shared/lib, src/shared/ui, src/features/characters/pages, src/features/audio-contents, src/features/series, src/features/community-posts LSP diagnostics 0건. npm run typecheck, npm run lint, npm run build는 exit 0이었다. npm run build는 기존 bundle size warning(502.94 kB chunk)을 표시했다.
  • 넓은 회귀 참고: npm run test:run -- src/features/characters src/features/audio-contents src/features/series src/features/community-postsaudio-list.test.tsx 1 failed 후 timeout됐고, 별도 crop 인접 재실행은 모두 통과해 이번 crop lifecycle 변경과 분리했다. E2E와 10MB 수동 확인은 사용자 지시에 따라 최종 회귀 단계에서 필요 시 실행한다.

Task R1.12 — fetch·XHR 교차 401 만료 전환 단일화

Goal 실행 P1-R12: 공통 fetch client와 Audio XHR upload가 동시에 401을 받아도 현재 session의 제거·login 이동을 합쳐 한 번만 수행하고, 새 로그인 session에서는 다음 만료를 다시 처리한다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-018

  • 시작 조건: PRD AUTH-005, §13의 동시 401 단일 처리, 기존 완료 P1-R6P4-R2의 transport별 401 burst contract.

  • 완료 증거: 같은 mutable session dependency를 사용하는 fetch request와 XHR upload가 동시에 401일 때 callback 2회를 재현하는 실패 test, transport 간 현재 session guard 최소 수정, auth/API·Audio upload focused 및 Phase 1 Gate 검증 기록.

  • Files: Modify: src/shared/api/client.ts, src/shared/api/__tests__/client-auth.test.ts, src/features/audio-contents/api/upload-audio-content.ts, src/features/audio-contents/tests/audio-upload.test.ts; Create: src/features/audio-contents/tests/audio-upload-auth-lifecycle.test.ts.

  • Interfaces: 두 transport는 401 처리 시 현재 protected session 존재 여부를 같은 getToken 의미로 확인하고, 첫 handler가 동기적으로 session을 제거한 뒤의 handler는 clearSession/onAuthExpired를 다시 호출하지 않는다. 새 login token이 생기면 다음 401 burst는 다시 처리한다.

  • 범위 밖: refresh token, 전역 event bus, router 교체, XHR→fetch 전환, 403 정책 변경.

  • Phase 1 Gate 연결: cross-transport RED/GREEN, 기존 transport별 malformed/동시 401 회귀, app/auth/shared unit, typecheck·lint·build가 통과하기 전에는 완료 처리하지 않는다.

  • 검증 기록: RED callback 실제 횟수, GREEN 뒤 동일·후속 session burst 횟수와 Gate 결과를 이 Task 하단과 Phase 1 리뷰에 누적한다.

  • RED: mutable token을 공유하는 createApiClient request와 uploadAudioContent를 동시에 401로 종료해 clearSession/onAuthExpired 각각 1회를 기대하고 현재 3회 호출 실패를 확인했다. 이어 새 token을 설정한 다음 burst는 다시 1회 처리돼야 한다.

  • GREEN: 두 transport가 callback 실행 직전 현재 session 존재를 확인하고 첫 clear 뒤 후속 handler가 만료 전환을 건너뛰도록 최소 수정했다.

  • REFACTOR: 기존 transport 내부 burst latch와 malformed response mapping을 유지하고 인증 coordinator나 새 dependency를 추가하지 않았다.

  • 실행 명령: npm run test:run -- src/shared/api/__tests__/client-auth.test.ts src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-upload-auth-lifecycle.test.ts; npm run test:run -- src/app src/features/auth src/shared/api src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-upload-auth-lifecycle.test.ts; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; git diff --check.

  • 기대 결과: 모든 명령 exit 0, 같은 session의 fetch+XHR 401 callback 각각 총 1회, 새 session의 다음 401 callback 각각 추가 1회, 기존 403·malformed body·upload cancel/retry 회귀 0건.

  • 수동 확인: upload 진행 중 다른 보호 조회도 401을 반환하게 해 로그인 이동·만료 안내·browser history 변화가 한 번인지 확인하고, 다시 로그인한 뒤 같은 만료 흐름이 재동작하는지 확인한다.

P1-R12 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/api/__tests__/client-auth.test.ts src/features/audio-contents/tests/audio-upload.test.ts — 1 failed / 19 passed. 신규 교차 transport test에서 clearSession 기대 2회 대비 실제 3회로 같은 session의 fetch+XHR 401 만료 전환 중복을 재현했다.
  • GREEN: client.tsupload-audio-content.ts가 401 callback 직전 현재 token 존재를 다시 확인하게 수정했다. 같은 focused 명령은 2 files / 20 tests passed였다.
  • REFACTOR/회귀: 새 coordinator나 dependency 없이 readToken helper만 중복 제거에 사용했고, lifecycle test를 별도 파일로 분리해 touched TS files를 250 LOC 이하로 유지했다. reviewer blocker로 fetch-first 순서를 추가한 뒤 auth 주입 시 storage fallback을 쓰지 않도록 보완했다. npm run test:run -- src/shared/api/__tests__/client-auth.test.ts src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-upload-auth-lifecycle.test.ts — 3 files / 22 tests passed. npm run test:run -- src/app src/features/auth src/shared/api src/features/audio-contents/tests/audio-upload.test.ts — 13 files / 91 tests passed. npm run test:run — 79 files / 397 tests passed. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, targeted git diff --check — 모두 exit 0. build는 기존 503.04kB chunk warning만 표시했다. LSP diagnostics는 변경 파일·디렉터리 기준 오류 0건이었다.
  • E2E/수동 확인: 사용자 지시에 따라 개발 중 E2E는 반복 실행하지 않고 모든 Task 구현 뒤 필요 시 수행한다.

Task R1.13 — crop frame과 저장 영역 시각적 일치 복구

Goal 실행 P1-R13: 고정 aspect image crop에서 운영자가 보는 frame과 canvas가 저장하는 source rectangle을 하나의 비변환 좌표계로 일치시킨다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-019

  • 시작 조건: PRD FILE-005~008, FILE-010, FILE-012, 기존 P1-R10 완료 기록과 현재 ImageCropDialog이 transformed <img>의 rect를 crop frame 크기로 사용하는 구조.

  • 완료 증거: 4:3 원본의 1:1과 210:297 고정 frame에서 보이는 영역과 저장 source rectangle 불일치를 재현하는 실패 test, 실제 crop viewport rect 기반 최소 수정, shared·4개 소비 도메인 회귀와 browser 수동 비교 기록.

  • Files: Modify: src/shared/ui/image-crop-dialog.tsx, src/shared/lib/crop-image.ts; Test: src/shared/ui/__tests__/image-crop-dialog.test.tsx, src/shared/lib/crop-image.test.ts, tests/e2e/character-workspace.spec.ts, tests/e2e/audio-content.spec.ts, tests/e2e/series.spec.ts, tests/e2e/community.spec.ts.

  • Interfaces: previewFrameWidth/Height는 CSS transform이 적용된 image rect가 아니라 실제 crop viewport를 의미한다. image cover scale, offset, zoom과 clamp는 한 번만 변환하고 resource별 aspect·최대 폭·no-upscale 계약은 유지한다.

  • 범위 밖: 외부 crop library, 회전·필터, 도메인별 보정값, 서버 file 계약 변경.

  • Phase 1 Gate 연결: shared crop focused unit, Character·Audio·Series·Community crop 회귀, mock Chromium crop journey, typecheck·lint·개발/운영 build가 모두 통과하기 전에는 완료하지 않는다.

  • RED: 4,000×3,000 원본에 1:1과 210:297 policy를 적용해 crop viewport의 실제 aspect, 이동·확대 후 저장 source rectangle, frame 밖 pixel 미포함을 기대하는 실패 test를 작성했다.

  • GREEN: 고정 aspect crop viewport를 보이게 렌더하고 그 viewport의 비변환 rect를 source 계산에 전달하는 최소 구현을 적용했다.

  • REFACTOR: preview→source 변환과 clamp를 shared crop 함수 한 곳에 유지하고 zoom이 getBoundingClientRect()와 수식에 중복 반영되지 않는지 회귀했다.

  • RED/GREEN/REFACTOR, focused·도메인 단위·정적 검증 결과를 Progress와 Phase 1 리뷰에 누적했다. E2E와 수동 pixel 비교는 사용자 지시에 따라 전체 Task 구현 후 필요 시 수행한다.

  • 실행 명령: npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx; npm run test:run -- src/features/characters src/features/audio-contents src/features/series src/features/community-posts; npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/series.spec.ts tests/e2e/community.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod.

  • 기대 결과: 모든 명령 exit 0, 고정 aspect frame과 저장 영역 불일치 0건, zoom/offset 중복 scale 0건, free aspect·no-upscale 회귀 0건.

  • 수동 확인: 4:3 대형 image를 Character/Audio 1:1과 Series 210:297에서 이동·확대한 뒤 적용 전 frame과 적용 후 preview가 같은 영역인지 비교한다.

P1-R13 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/ui/__tests__/image-crop-dialog.test.tsx — 1 failed / 9 passed. 신규 ImageCropDialog measures the visible crop viewport instead of the transformed image이미지 crop viewport label 부재로 실패해 고정 crop viewport가 없는 현재 구조를 재현했다.
  • GREEN: ImageCropDialog에 고정 aspect viewport를 렌더하고 applyCrop이 image rect 대신 viewport rect를 previewFrameWidth/Height로 전달하게 했다. 1차 reviewer blocker였던 image box/frame 불일치는 source aspect 기준 cover image 배치로 보완했다.
  • REFACTOR/회귀: src/shared/lib/crop-image.test.ts에 4,000×3,000 원본의 1:1·210:297 viewport+zoom source rect 회귀를 추가하고, zero-overhang 축에서 화면 offset과 저장 offset이 함께 clamp되는 dialog 회귀를 추가했다. npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx — 2 files / 20 tests passed. npm run test:run -- src/features/characters src/features/audio-contents src/features/series src/features/community-posts — 30 files / 183 tests passed.
  • 정적 검증: npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. build는 기존 500kB chunk warning을 표시했다. LSP diagnostics는 image-crop-dialog.tsx, image-crop-dialog.test.tsx, crop-image.test.ts 오류 0건이다.
  • 리뷰어 게이트: 1차 review에서 cover geometry와 offset clamp blocker 2건을 지적받아 보완했고, 2차 delta review에서 APPROVED 판정을 받았다.
  • 환경 정리: 검증 중 /var/.../T 여유 공간 부족으로 Vitest가 ENOSPC를 반환해, 워크스페이스 밖 /var/.../T/opencode의 재생성 가능한 DerivedData/cache 임시 폴더를 삭제했다. df 기준 여유 공간은 531MiB에서 15GiB로 회복된 뒤 검증을 재실행했다.
  • E2E/수동: 개발 중 E2E는 사용자 지시에 따라 매 Task마다 수행하지 않고 전체 Task 구현 후 필요 시 수행한다. 실제 pixel 수동 비교도 같은 최종 확인 범위로 남긴다.

Task R1.14 — Character 생성·수정 route 보호 인가 probe 복구

Goal 실행 P1-R14: /ai-characters/new/ai-characters/:characterId/edit도 다른 관리자 route와 같이 서버 보호 route probe의 401/403 정책을 먼저 적용한다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-020

  • 시작 조건: P1-R13 완료, PRD AUTH-005~006, isAiCharactersRoute()가 Character list/detail과 하위 resource route는 포함하지만 create/edit route는 제외하는 현재 구조.

  • 완료 증거: create/edit 직접 URL의 probe 403에서 접근 거부 화면, 401에서 session 정리·login 이동, probe 완료 전 하위 form/API 미실행을 고정하는 실패 test와 Phase 1 Gate 기록.

  • Files: Modify: src/app/browser-location.ts, 필요 시 src/app/App.tsx; Create: src/app/browser-location.test.ts; Modify: src/app/App.protected-errors.test.tsx, src/app/App.test.tsx.

  • Interfaces: isAiCharactersRoute 보호 route 판정은 list, create, detail, edit와 모든 하위 resource route를 포함한다. 403은 session을 유지한 채 /access-denied, 401은 session을 제거하고 /login으로 이동하는 기존 계약을 변경하지 않는다.

  • 범위 밖: router library 도입, 서버 인증 계약 변경, role refresh, 새 access-denied 화면.

  • RED: create/edit 직접 route에서 보호 probe 403을 반환할 때 form이 노출되거나 일반 error로 남는 현재 실패와, 401의 login 이동을 재현했다.

  • GREEN: 두 route를 보호 route 판정에 포함하는 최소 수정으로 기존 probe 흐름을 재사용했다.

  • REFACTOR: route matcher 진리표를 unit test로 고정하고 path 문자열 중복과 별도 auth abstraction을 추가하지 않았다.

  • RED/GREEN/REFACTOR, focused·auth/app 회귀·정적/build 결과를 Progress와 Phase 1 리뷰에 누적했다. stale ADMIN 수동 확인은 실제 credential이 필요한 외부 수동 QA로 남긴다.

  • 실행 명령: npm run test:run -- src/app/browser-location.test.ts src/app/App.protected-errors.test.tsx src/app/App.test.tsx; npm run test:run -- src/app src/features/auth src/shared/api; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod.

  • 기대 결과: 모든 명령 exit 0, create/edit probe 전 form·하위 request 0건, 403 접근 거부·401 login 전환 회귀 0건.

  • 수동 확인: stale ADMIN session으로 create/edit URL을 직접 열고 개발 API 403에서 form이 잠시라도 노출되지 않으며 접근 거부 화면으로 이동하는지 확인한다.

P1-R14 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/app/browser-location.test.ts src/app/App.protected-errors.test.tsx src/app/App.test.tsx — 2 failed files / 5 failed / 18 passed. isAiCharactersRoute가 create/edit를 false로 판정했고, /ai-characters/new, /ai-characters/101/edit의 401/403 probe 전환 대신 form 또는 detail error가 노출되는 실패를 재현했다.
  • GREEN: isAiCharactersRouteroutePaths.aiCharacterCreategetCharacterEditIdFromPath(path)를 추가해 기존 보호 route probe를 재사용했다. focused npm run test:run -- src/app/browser-location.test.ts src/app/App.protected-errors.test.tsx src/app/App.test.tsx — 3 files / 23 tests passed.
  • REFACTOR/회귀: 새 src/app/browser-location.test.ts의 list/create/detail/edit 진리표로 matcher를 고정했다. npm run test:run -- src/app src/features/auth src/shared/api — 13 files / 84 tests passed.
  • 정적 검증: LSP diagnostics browser-location.ts, browser-location.test.ts, App.protected-errors.test.tsx 오류 0건. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. build는 기존 500kB chunk warning을 표시했다.
  • 리뷰어 게이트: P1-R14 delta review에서 create/edit 보호 probe 합류, 401/403 form 미노출 test, 문서 기록 범위가 기준을 충족해 APPROVED 판정을 받았다.
  • 수동 확인: stale ADMIN 개발 API credential이 필요한 외부 수동 QA는 자동 검증 범위 밖으로 유지한다.

Task R1.15 — 잘못된 percent-encoding route 안전 처리

Goal 실행 P1-R15: 잘못된 percent-encoding이 포함된 직접 URL이 URIError로 SPA를 중단하지 않고 안전한 기본 route로 정규화되게 한다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-021

  • 시작 조건: P1-R14 완료, browser-location.ts의 route regex가 %와 같은 segment를 허용하고 모든 parser가 decodeURIComponent 예외를 처리하지 않는 현재 구조.

  • 완료 증거: 단일·복합 param route의 malformed % 재현 test, 예외 없는 공통 decode helper와 /ai-characters fallback, 정상 한글/ASCII ID route 회귀 및 Phase 1 Gate 기록.

  • Files: Modify: src/app/browser-location.ts, src/app/browser-location.test.ts, src/app/App.test.tsx.

  • Interfaces: route param decode 실패는 null로 판정해 readRoutePath()/ai-characters로 정규화하고, 정상 percent-encoded ID와 navigation API는 유지한다.

  • 범위 밖: 404 page 신설, URL schema 재설계, router dependency, 백엔드 ID 제약 추정.

  • RED: character/audio/series 단일·복합 param에 %, %E0%A4%A를 넣었을 때 URIError가 발생하는 실패 test를 작성했다.

  • GREEN: 예외를 내부에서 종료하는 작은 decode helper로 parser를 공통화하고 잘못된 path를 기본 목록으로 보냈다.

  • REFACTOR: 모든 decodeURIComponent 호출이 helper 하나를 통하도록 정리하고 정상 인코딩 route 회귀를 실행했다.

  • RED/GREEN/REFACTOR, focused·app 회귀·정적/build 결과를 Progress와 Phase 1 리뷰에 누적했다. malformed URL 수동 확인은 최종 browser 확인 범위로 남긴다.

  • 실행 명령: npm run test:run -- src/app/browser-location.test.ts src/app/App.test.tsx; npm run test:run -- src/app; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod.

  • 기대 결과: 모든 명령 exit 0, malformed encoding 예외 0건, /ai-characters 안전 fallback, 정상 route 회귀 0건.

P1-R15 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/app/App.test.tsx — 1 failed file / 3 failed / 8 passed. /ai-characters/%, /ai-characters/%E0%A4%A/audio-contents/1, /ai-characters/101/series/%E0%A4%A가 각각 URIError: URI malformed로 실패했다.
  • GREEN: browser-location.tsdecodeRouteSegment helper를 추가하고 모든 route param decode가 malformed segment에서 null을 반환하게 했다. npm run test:run -- src/app/browser-location.test.ts src/app/App.test.tsx — 2 files / 12 tests passed.
  • REFACTOR/회귀: rg -n "decodeURIComponent" src/app/browser-location.ts — helper 내부 1건만 남았다. npm run test:run -- src/app — 5 files / 37 tests passed.
  • 정적 검증: LSP diagnostics browser-location.ts, App.test.tsx 오류 0건. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. build는 기존 500kB chunk warning을 표시했다.
  • 리뷰어 게이트: P1-R15 review에서 malformed route 예외 처리, helper 단일화, scope 제한, 문서 기록이 기준을 충족해 APPROVED 판정을 받았다.
  • 수동 확인: malformed URL browser 직접 입력 확인은 전체 Task 구현 후 필요 시 수행하는 E2E/수동 QA 범위로 남긴다.
  • 수동 확인: 로그인/비로그인 상태에서 /ai-characters/%를 직접 열어 white screen 없이 안전한 화면으로 정규화되는지 확인한다.

Task R1.16 — malformed route의 보호 probe 경계 복구

Goal 실행 P1-R16: decode할 수 없는 percent-encoding route를 canonical Character 목록 snapshot으로 정규화해 기존 ADMIN 보호 probe를 거친 뒤에만 shell을 렌더한다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-022

  • 시작 조건: P1-R15 완료 기록과 readRoutePath()가 regex에 맞는 malformed path를 raw RoutePath로 반환해 isAiCharactersRoute()가 false가 되는 재리뷰 근거.

  • 완료 증거: malformed 단일·복합 param route가 보호 probe 403에서 shell을 노출하지 않고 /access-denied로 전환되는 실패 test, canonical fallback snapshot 최소 수정, 정상 인코딩·create/edit·하위 route 회귀와 Phase 1 Gate 기록.

  • Files: Modify: src/app/browser-location.ts, src/app/App.protected-shell.test.tsx; Test: src/app/browser-location.test.ts.

  • Interfaces: route regex 일치 뒤 필수 segment 중 하나라도 decode 실패하면 readRoutePath()routePaths.aiCharacters를 반환한다. 정상 encoded route와 navigateTo API는 유지한다.

  • 범위 밖: router dependency, 404 page, backend ID 제약, 보호 probe 자체 재설계.

  • Phase 1 Gate 연결: malformed 401/403 focused test, app/auth/api 회귀, typecheck·lint·개발/운영 build가 모두 통과하기 전에는 완료하지 않는다.

  • RED: ADMIN session과 malformed 단일 route에서 보호 probe 403을 반환할 때 현재 shell/list fallback이 먼저 렌더되는 실패 test를 작성했다.

  • GREEN: readRoutePath()의 route 후보를 반환하기 전에 isAiCharactersRoute()를 통과시키고 실패 시 Character 목록 snapshot으로 귀결시킨다.

  • REFACTOR: 정상 create/edit route parser를 유지하고 별도 auth abstraction이나 route table 재설계를 추가하지 않았다.

  • RED/GREEN/REFACTOR와 focused 결과를 Progress와 Phase 1 리뷰에 누적했다. Phase 1 정적/build Gate는 P9-R11, P10-R10까지 완료한 뒤 사용자 지시에 따라 통합 실행한다.

  • 실행 명령: npm run test:run -- src/app/browser-location.test.ts src/app/App.protected-errors.test.tsx src/app/App.test.tsx; npm run test:run -- src/app src/features/auth src/shared/api; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod.

  • 기대 결과: 모든 명령 exit 0, malformed route의 probe 우회·protected shell 선노출 0건, 401 login·403 access-denied 전환과 정상 route 회귀 0건.

  • 수동 확인: stale ADMIN session으로 /ai-characters/%를 직접 열고 보호 probe가 끝나기 전 shell이 보이지 않으며 403 응답에서 접근 거부 화면으로 이동하는지 확인한다.

P1-R16 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/app/App.protected-shell.test.tsx — 1 failed / 7 passed. 신규 malformed route test가 기대한 보호 route 확인 중 대신 AI 캐릭터 목록을 불러오는 중을 받아 shell/list fallback 선노출을 재현했다.
  • GREEN/REFACTOR: readRoutePath()가 route regex 후보를 raw path로 반환하기 전에 isAiCharactersRoute()를 통과시키고, decode 실패로 보호 route 판정이 불가능하면 canonical /ai-characters snapshot으로 귀결되게 했다. 새 router dependency, auth abstraction, route table 재설계는 추가하지 않았다.
  • Focused 검증: npm run test:run -- src/app/App.protected-shell.test.tsx src/app/browser-location.test.ts — 2 files / 9 tests passed. 수동 QA와 E2E는 사용자 지시에 따라 전체 Task 구현 후 필요 시 실행한다.
  • 통합 검증: npm run test:run — 81 files / 411 tests passed. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0.

Task R1.17 — malformed route 인가 회귀 matrix·focused Gate 보강

Goal 실행 P1-R17: P1-R16의 canonical fallback을 단일·복합 malformed route와 401/403 전체 인가 경계에서 고정하고 실제 regression spec이 focused Gate에 포함되게 한다.

  • 연결 리뷰: Phase 1 현재 리뷰REV-P1-023

  • 시작 조건: P1-R16 제품 코드는 focused·full unit에서 통과하지만 신규 test는 /ai-characters/%+403 한 건뿐이고 Task 실행 명령은 App.protected-shell.test.tsx를 누락한 재리뷰 근거.

  • 완료 증거: malformed 단일·Audio/Series 복합 route의 401/403 matrix에서 probe pending 중 shell 미노출, 401 login·session 제거, 403 access-denied·session 유지와 정상 encoded route 회귀, 실제 regression spec을 포함한 focused 명령 결과.

  • Files: Modify: src/app/App.protected-shell.test.tsx, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase1-platform-auth-shared-ui.md; Test: 기존 src/app/App.test.tsx, src/app/App.protected-errors.test.tsx, src/app/browser-location.test.ts.

  • Interfaces: 제품 route parser와 App 보호 probe는 변경하지 않는다. test는 기존 MSW handler·session helper를 재사용하고 status/path matrix만 확장한다. matrix가 실제 제품 결함을 재현할 때만 root-cause 코드 수정을 이 Task에 포함한다.

  • 범위 밖: router dependency, 별도 auth abstraction, backend ID 제약, E2E browser 확대.

  • Phase 1 Gate 연결: malformed 인가 matrix, app/auth/api 회귀, typecheck·lint·개발/운영 build가 모두 통과하기 전에는 완료하지 않는다.

  • TDD 예외 사유: root-cause 제품 수정은 P1-R16에서 이미 GREEN이며 이번 산출물은 누락된 regression matrix와 실행 명령이다. 현재 test inventory와 focused 명령 누락을 RED 대체 증거로 사용했다.

  • 단일·복합 malformed path × 401/403을 data-driven test로 고정하고 pending 중 protected shell·logout 미노출과 최종 auth 전환을 검증했다.

  • P1-R16 실행 명령에 App.protected-shell.test.tsx를 포함하고 정상 encoded/create/edit route 회귀를 함께 실행했다.

  • focused·Phase 1 회귀·정적/build 결과와 실제 test 수를 Progress와 Phase 1 리뷰에 누적했다.

  • 대체 검증 방법: 현재 test matrix inventory, 필요 시 guard negative-control, focused/full unit과 정적/build Gate.

  • 실행 명령: npm run test:run -- src/app/App.protected-shell.test.tsx src/app/App.protected-errors.test.tsx src/app/App.test.tsx src/app/browser-location.test.ts; npm run test:run -- src/app src/features/auth src/shared/api; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; git diff --check.

  • 기대 결과: 모든 명령 exit 0, malformed 단일·복합 401/403 matrix 누락 0건, probe 전 shell·logout 노출 0건, 401/403·정상 route 회귀 0건.

  • 수동 확인: 없음. 보호 경계는 deterministic MSW pending/status matrix로 검증하며 실제 stale ADMIN 개발 API 확인은 별도 수동 QA다.

P1-R17 수정 검증 기록 (2026-07-31):

  • RED 대체: REV-P1-023에서 /ai-characters/%+403 한 건과 목록 heading만 보는 복합 malformed route test, focused 명령의 App.protected-shell.test.tsx 누락을 확인했다. 제품 guard는 P1-R16에서 이미 GREEN이라 production code는 변경하지 않았다.
  • GREEN focused: src/app/App.protected-shell.test.tsx에 단일·Audio 복합·Series 복합 malformed path × 401/403 matrix를 추가했다. npm run test:run -- src/app/App.protected-shell.test.tsx — 1 file / 14 tests passed.
  • REFACTOR/회귀: npm run test:run -- src/app/App.protected-shell.test.tsx src/app/App.protected-errors.test.tsx src/app/App.test.tsx src/app/browser-location.test.ts — 4 files / 40 tests passed. npm run test:run -- src/app src/features/auth src/shared/api — 13 files / 94 tests passed.
  • 정적/build: npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. build는 기존 500kB 초과 chunk warning만 표시했다. E2E와 실제 stale ADMIN 개발 API 수동 QA는 사용자 지시에 따라 전체 Task 이후 필요 시/credential 준비 시로 유지한다.

Phase 2. 개발 전용 Mock Preview 기반

목표: backend endpoint 구현 전에도 제공된 API Contract 범위의 최종 UI를 브라우저에서 확인하고, 명시적 전환으로 실제 개발 API와 같은 frontend code를 사용한다.

Phase Goal P2: Task 2.1 → 2.3과 Phase 2 Gate로 browser MSW 기반 mock preview와 server mode 경계를 완성한다.

  • 시작 조건: P1-GATE 완료.
  • 완료 조건: P2-T1~P2-T3, P2-GATE 완료. mock/server 분리와 production 차단이 자동 test와 E2E로 고정됨.
  • 현재 상태: 완료. P2-T1P2-T3, P2-GATE, P2-R1P2-R16의 구현·수정·검증이 끝났으며 Phase 3 진행 가능.

요구사항: MOCK-001~009, PRD 11.1~11.2, 12, 13.

주요 Files:

  • Modify: package.json, .env.example, src/shared/config/env.ts, src/shared/config/env.test.ts
  • Modify: src/app/App.tsx, src/main.tsx, vite.config.ts, playwright.config.ts
  • Modify: src/app/App.test.tsx, src/app/admin-pages.tsx, src/app/browser-location.ts
  • Create: src/app/protected-admin-shell.tsx
  • Create: src/shared/mocks/{browser,handlers,contract}.ts
  • Create: src/shared/mocks/__tests__/{mode-boundary,auth-handlers,mock-preview-docs,production-graph}.test.ts
  • Create: src/shared/mocks/browser.test.ts
  • Create: src/shared/ui/mock-mode-banner.tsx
  • Create: src/shared/ui/__tests__/mock-mode-banner.test.tsx
  • Create: public/mockServiceWorker.js
  • Create: tests/e2e/{mock-mode-boundary,mock-preview-shell,server-mode-boundary}.spec.ts
  • Modify when implementation is complete: README.md, docs/agent-guide/{environment,scripts}.md

Task 2.1 explicit API mode·production 경계

Goal 실행 P2-T1: server | mock mode와 dev-only browser worker bootstrap을 구성하고 자동 fallback·production mock을 차단한다.

  • 시작 조건: P1-GATE 완료, PRD MOCK-001~006 확인.

  • 완료 증거: env/script/bootstrap focused test, dev server 기본값, dev:mock explicit mode, production mock 거부와 404 no-fallback test.

  • 범위 밖: Character 이후 도메인 fixture와 실제 backend endpoint 구현.

  • VITE_API_MODE=server | mock을 검증하고 누락 기본값은 server로 고정하는 실패 test를 작성한다.

  • npm run dev:mock만 Vite mock mode와 browser worker를 시작하고 기본 npm run dev는 실제 VITE_API_BASE_URL을 사용하는 script test를 작성한다.

  • Playwright가 같은 spec을 explicit mode로 실행할 수 있도록 e2e:mock과 server mode 명령을 정의하고 각 webServer 환경을 test한다.

  • import.meta.env.DEV && apiMode === "mock"일 때만 browser module을 dynamic import하고 worker 준비 후 React를 mount한다.

  • production mode에서 mock을 설정하면 build/start 전에 설명 가능한 오류로 거부하고 worker·fixture가 production graph에서 실행되지 않는 test를 작성한다.

  • server mode의 404·network error가 browser mock을 시작하거나 응답을 교체하지 않는 test를 작성한다.

  • worker의 unhandled request 정책은 error로 두어 누락된 handler가 실제 backend로 조용히 통과하지 않게 한다.

P2-T1 구현 검증 기록 (2026-07-27):

  • RED: npm run test:run -- src/shared/config/env.test.ts src/shared/mocks/__tests__/mode-boundary.test.ts는 mode/default/script 미구현으로 2 files 중 5 tests가 기대대로 실패했고, VITE_API_MODE=mock npm run e2e -- tests/e2e/mock-mode-boundary.spec.ts는 worker 미등록과 unhandled 정책 미구현으로 8 tests가 실패했다.
  • GREEN focused: npm run test:run -- src/shared/config/env.test.ts src/shared/mocks/browser.test.ts src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/api/__tests__/client.test.ts는 4 files / 18 tests 통과, npm run e2e:mock -- tests/e2e/mock-mode-boundary.spec.ts는 4 browser projects 통과, npm run e2e -- tests/e2e/server-mode-boundary.spec.ts는 4 browser projects 통과했다.
  • Production boundary: VITE_API_MODE=mock npm run build:prod는 guard 추가 전에는 부당하게 성공했고, guard 추가 후 VITE_API_MODE=mock is only available during development 오류로 기대대로 거부됐다.
  • Quality: npm run typecheck, npm run lint, npm run test:run(31 files / 117 tests), npm run build:dev, npm run build:prod, git diff --check를 성공했다. Playwright로 http://127.0.0.1:8888/loginhttp://127.0.0.1:8889/login을 직접 열어 server mode는 worker registration 0건, mock mode는 /mockServiceWorker.js controller 등록을 확인했고, QA용 dev server 포트 8888·8889가 비었음을 확인했다.

P2-T1 리뷰 보강 기록 (2026-07-27):

  • RED: npm run test:run -- src/shared/mocks/__tests__/production-graph.test.ts src/shared/mocks/__tests__/mode-boundary.test.ts는 production build 산출물에 mockServiceWorker.js가 복사되어 실패했고, npm run e2e -- tests/e2e/server-mode-boundary.spec.ts는 404·network error 후 no-fallback 증거가 없어 8 tests가 실패했다.
  • GREEN: production mode에서 publicDir 복사를 끄고 production graph test를 실제 NODE_ENV=production build 조건으로 맞춘 뒤 같은 focused unit은 2 files / 3 tests 통과했다. server mode E2E는 404·network error 요청 후에도 browser MSW registration 0건임을 확인해 4 browser projects / 12 tests 통과했다.
  • Re-review 보강: production graph test가 output file path까지 검사하도록 보강했고, server 404·network error는 오류 alert 표시 후 worker registration 0건을 확인하도록 App의 route error 표시 조건을 수정했다. npm run test:run -- src/shared/mocks/__tests__/production-graph.test.ts src/shared/mocks/__tests__/mode-boundary.test.ts src/app/App.test.tsx는 3 files / 16 tests 통과했고, npm run e2e -- tests/e2e/server-mode-boundary.spec.ts는 4 browser projects / 12 tests 통과했다.
  • Guide sync: VITE_API_MODE, dev:mock, e2e:mock, production mock 거부 기준을 docs/agent-guide/environment.mddocs/agent-guide/scripts.md에 반영했다.

Task 2.2 공통 preview session·fixture store

Goal 실행 P2-T2: production API 경계를 그대로 사용하는 deterministic auth fixture, in-memory store와 mock mode 안내를 완성한다.

  • 시작 조건: P2-T1 완료, PRD AUTH-001~013과 Phase 1에서 검증한 기존 인증 계약 확인. 인증 operation은 현 OpenAPI 범위 밖이며 EXT-006으로 유지한다.

  • 완료 증거: auth handler/store/banner focused test와 login → protected shell mock preview E2E 기록.

  • 범위 밖: 도메인별 endpoint handler와 계약 미제공 fixture.

  • fixture는 정규화 ApiResponse<T>와 실제 endpoint·header·body contract를 사용하고 별도 mock DTO를 만들지 않는다.

  • ADMIN login·logout, 401·403 fixture와 새로고침 시 seed로 초기화되는 in-memory store를 만든다.

  • mock mutation은 같은 store의 목록·상세 응답을 갱신하고 browser storage에 domain fixture를 영구 저장하지 않는다.

  • mock mode임을 지속적으로 표시하는 접근 가능한 banner와 server mode에서 banner가 없는 test를 작성한다.

  • JWT·password·signed URL·multipart body가 mock logger와 storage에 기록되지 않는 test를 작성한다.

  • handler와 fixture가 src/shared/test/server.ts의 Node test lifecycle을 변경하지 않고 필요한 contract factory만 공유하게 한다.

P2-T2 실행 기록 (2026-07-27):

  • RED: npm run test:run -- src/shared/mocks/__tests__/auth-handlers.test.ts src/shared/ui/__tests__/mock-mode-banner.test.tsx src/app/App.test.tsx src/shared/mocks/browser.test.ts@/shared/mocks/handlers·@/shared/ui/mock-mode-banner 미구현, App mock banner 부재, browser worker handler 미등록 기대 실패로 4 files 중 5 failures가 발생했다. npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts --project=chromium은 mock login handler 부재로 /login에 머물러 기대대로 실패했다.
  • GREEN: createMockHandlers(createMockStore())POST /admin/member/login, POST /member/logout, GET /api/v2/admin/ai-characters?page=0&size=20을 정규화 envelope로 처리하고 logout mutation 후 같은 store에서 token을 폐기하며 새 store 생성 시 seed로 초기화한다. 리뷰 보강으로 logout 후 재login 시 같은 store에서 ADMIN token이 다시 활성화되도록 고정했다. 같은 focused unit 명령은 4 files / 25 tests 통과했고, npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts는 4 browser projects / 8 tests 통과했다.
  • Sensitive data/storage: handler는 logger를 추가하지 않았고, focused test에서 login password/JWT 요청 후 localStorage, mock sessionStorage, cookie, IndexedDB, console log/warn/error에 fixture domain data와 민감값을 기록하지 않음을 확인했다. src/shared/test/server.ts lifecycle은 변경하지 않았다.

Task 2.3 Mock Preview 반응형·문서화

Goal 실행 P2-T3: mock mode의 browser 접근성·반응형과 실행 문서를 검증해 사용자가 최종 UI 확인 mode를 명확히 선택하게 한다.

  • 시작 조건: P2-T1, P2-T2 완료.

  • 완료 증거: 320px·desktop mock shell E2E, axe 결과, README와 environment/scripts 가이드의 실제 명령 동기화 기록.

  • 범위 밖: 도메인별 최종 UI와 server integration 완료 주장.

  • mock banner가 320px·200% zoom에서 핵심 control을 가리지 않고 axe critical·serious 위반이 없는지 E2E로 확인한다.

  • README에 npm run devnpm run dev:mock, mode 차이, mock data reset, production 금지와 no-auto-fallback을 기록한다.

  • 구현이 완료된 뒤 docs/agent-guide/environment.mdscripts.md에 실제 환경 변수와 명령을 추가한다.

  • 후속 도메인 Phase가 handler·fixture·mock E2E를 소유한다는 규칙을 문서화한다.

P2-T3 실행 기록 (2026-07-27):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts src/shared/mocks/__tests__/mode-boundary.test.ts는 README에 실제 dev/dev:mock/e2e/e2e:mock script, mode 차이, mock data reset, production 금지, no-auto-fallback 기록이 없고 docs/agent-guide/environment.mdscripts.md에 reset·domain handler/fixture/mock E2E 소유 규칙이 없어 2 tests가 기대대로 실패했다. npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts --project=chromium은 새 320px·200% zoom 및 axe 확인을 포함해 5 tests가 통과해 기존 mock shell UI는 문서 보강 전에도 요구 접근성 동작을 만족함을 확인했다.
  • GREEN: README에 npm run dev/dev:mock/e2e/e2e:mock의 실제 명령, server mode와 mock mode 차이, mock data reset, production mock 거부, no-auto-fallback, 후속 도메인 handler·fixture·mock E2E 소유 규칙을 추가했다. docs/agent-guide/environment.md에는 VITE_API_MODE=server | mock, reset, production 금지, no-auto-fallback을 동기화하고 docs/agent-guide/scripts.md에는 실제 script와 domain ownership rule을 동기화했다. 같은 focused unit 명령은 2 files / 4 tests 통과했고, npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts --project=chromium은 5 tests 통과했다. 최종 확인으로 npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts는 4 browser projects / 20 tests 통과, npm run typecheck, npm run lint, git diff --check도 통과했다.

Phase 2 Gate

Goal 실행 P2-GATE: mock/server mode 경계, auth preview와 production 차단을 최종 판정한다.

  • 시작 조건: P2-T1~P2-T3 완료.
  • 완료 증거: 아래 명령과 Expected 통과 및 Phase 2 Progress 기록.
  • 범위 밖: mock 성공을 실제 도메인 server integration 성공으로 간주하는 것.
npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx
npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts
npm run typecheck
npm run lint
npm run build:dev
npm run build:prod

Expected: npm run dev:mock에서는 ADMIN login → protected shell과 mock banner가 동작하고 실제 backend 요청은 0건이다. 기본 server mode는 실제 API 오류를 그대로 처리하며 production build는 browser mock을 활성화하지 않는다.

P2-GATE 실행 기록 (2026-07-27):

  • npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx — 7 files / 20 tests 통과.
  • npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts — 4 browser projects / 20 tests 통과. mock login → protected shell, logout 후 재login, 320px·200% zoom banner/control, axe critical·serious 0을 확인했다.
  • npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0.
  • LSP diagnostics: src/shared/mocks/__tests__/mock-preview-docs.test.ts clean, tests/e2e directory 6 files / 0 diagnostics. 단일 mock-preview-shell.spec.ts LSP 호출은 3초 fresh diagnostics timeout이었으나 directory diagnostics와 typecheck/lint로 보완했다.
  • 판정: Phase 2 mock/server mode 경계, auth preview, production build 경계, mock preview 반응형·접근성·문서화 완료. mock 통과는 후속 도메인 server integration 완료로 간주하지 않는다.

Task R2.1 — 보호 route 검증 실패 시 fail-closed 복구

Goal 실행 P2-R1: Phase 2 no-auto-fallback 오류 UI가 Phase 1의 보호 shell 비노출 경계를 우회하지 않게 하고 회귀를 방지한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-001

  • 시작 조건:

    • 저장된 ADMIN session으로 /ai-characters에 진입한 뒤 probe가 404 또는 network error로 실패할 때 보호 shell이 노출되는 현재 동작을 실패 test로 재현한다.
  • 완료 증거:

    • probe 성공 전과 401·403·404·network error에서 보호 shell 비노출
    • 404·network error는 mock fallback 없이 보호 shell 밖의 오류 UI로 표시
    • focused App test, server mode boundary E2E, auth E2E와 P2-GATE 실행 기록
  • 범위 밖:

    • 인증 방식, session 저장 방식 또는 API client 전면 교체
    • Phase 3 Character 화면 구현
  • 404·network error와 이전 오류 뒤 새 session probe pending에서 보호 shell이 노출되는 실패 test를 추가한다.

  • 성공한 현재 token probe만 보호 shell을 열고 실패 오류는 shell 밖에서 표시하는 최소 상태 전이를 구현한다.

  • src/app/App.test.tsx, tests/e2e/server-mode-boundary.spec.ts, 기존 auth E2E와 P2-GATE를 실행한다.

  • 결과를 plan-task.md 검증 기록과 Phase 2 리뷰 문서에 누적한다.

P2-R1 수정 검증 기록 (2026-07-27):

  • RED: npm run test:run -- src/app/App.test.tsx src/shared/mocks/__tests__/auth-handlers.test.ts — 404·network error에서 보호 shell banner/main이 렌더되어 App test 2건이 기대대로 실패했다.
  • GREEN focused: npm run test:run -- src/app/App.test.tsx src/shared/mocks/__tests__/auth-handlers.test.ts src/shared/mocks/browser.test.ts — 3 files / 27 tests 통과.
  • Surface: npm run e2e -- tests/e2e/server-mode-boundary.spec.ts — 4 browser projects / 12 tests 통과. 404·network error 후 mock worker 0건, 보호 shell main 0건, logout button 0건을 확인했다.
  • Regression: npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts — 4 browser projects / 16 tests 통과.
  • Reviewer blocker 보강: 같은 token 재사용 logout → login 뒤 probe 실패 시 이전 검증 token이 남는 회귀를 추가로 확인했다. RED npm run test:run -- src/app/App.test.tsx — 1 test failed. GREEN 같은 command — 1 file / 19 tests 통과. 보강 후 npm run test:run -- src/app/App.test.tsx src/shared/mocks/__tests__/auth-handlers.test.ts src/shared/mocks/browser.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 4 files / 31 tests 통과, npm run e2e -- tests/e2e/server-mode-boundary.spec.ts — 12 tests 통과, npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts — 20 tests 통과.

create_goal objective 초안 — P2-R1

  • objective: P2-R1의 확정 review 항목 REV-P2-001을 수정하고 Phase 1 보호 route 경계의 회귀를 방지한다. plan-task.md에 추가된 Task R2.1만 수행한다.
  • 완료 조건: 404·network error를 포함한 probe 비성공 상태에서 보호 shell 비노출, 독립 오류 UI, focused test, server/auth E2E, P2-GATE와 누적 기록이 모두 확인된다.
  • 금지 조건: 인증·session 아키텍처 교체나 Phase 3 Character 구현을 포함하지 않는다.
  • 중단 조건: 보호 route 확인 endpoint 자체가 변경됐다는 외부 계약이 확인되면 계약 문서를 먼저 갱신한다.

Task R2.2 — Mock Preview API origin·JWT 오류 계약 복구

Goal 실행 P2-R2: browser mock handler를 설정된 API base URL에만 결합하고 invalid·revoked JWT logout을 계약 status로 반환한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-002, REV-P2-003

  • 시작 조건:

    • 잘못된 origin의 login이 200을 반환하고 invalid·revoked JWT logout이 200을 반환하는 현재 동작을 각각 실패 test로 재현한다.
  • 완료 증거:

    • 설정된 VITE_API_BASE_URL의 정확한 URL·method만 handler가 처리하고 다른 origin은 onUnhandledRequest: "error" 경계에 남음
    • invalid·revoked JWT logout은 Phase 1에서 검증한 기존 인증 계약의 401 오류 envelope를 반환
    • 정상 login → logout → login 흐름과 403 fixture 회귀 없음
    • focused handler test, mock preview E2E와 P2-GATE 실행 기록
  • 범위 밖:

    • 계약 미제공 endpoint·DTO·회원 role 정책 추정
    • 도메인별 fixture 선행 구현
  • wrong-origin login과 invalid·두 번째 logout의 현재 200 응답을 실패 test로 고정한다.

  • handler 생성 시 API base URL을 주입해 production endpoint URL에만 매칭한다.

  • token access 상태를 확인한 뒤 logout store를 변경하고 invalid·revoked token에 401을 반환한다.

  • auth handler focused test, mock preview E2E와 P2-GATE를 실행한다.

  • 결과를 plan-task.md 검증 기록과 Phase 2 리뷰 문서에 누적한다.

P2-R2 수정 검증 기록 (2026-07-27):

  • RED: npm run test:run -- src/app/App.test.tsx src/shared/mocks/__tests__/auth-handlers.test.ts — wrong-origin login이 200, invalid JWT logout이 200으로 응답해 auth handler test 2건이 기대대로 실패했다.
  • GREEN focused: npm run test:run -- src/app/App.test.tsx src/shared/mocks/__tests__/auth-handlers.test.ts src/shared/mocks/browser.test.ts — 3 files / 27 tests 통과. createMockHandlers(store, apiBaseUrl) exact origin, invalid·revoked JWT 401, browser worker runtime env 주입을 확인했다.
  • Surface: npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts — 4 browser projects / 20 tests 통과. 정상 mock login → protected shell, logout 후 재login, 320px·200% zoom, axe critical·serious 0건을 확인했다.
  • P2 focused Gate: npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx — 7 files / 23 tests 통과.

create_goal objective 초안 — P2-R2

  • objective: P2-R2의 확정 review 항목 REV-P2-002와 REV-P2-003을 수정하고 mock/production API 계약 경계를 일치시킨다. plan-task.md에 추가된 Task R2.2만 수행한다.
  • 완료 조건: exact API origin, invalid·revoked JWT 401, 기존 정상 auth preview, focused test, mock E2E, P2-GATE와 누적 기록이 모두 확인된다.
  • 금지 조건: 계약 미제공 endpoint·DTO·회원 role 의미를 추정하거나 도메인 fixture를 추가하지 않는다.
  • 중단 조건: logout의 non-ADMIN token 정책이 현재 계약만으로 판정되지 않으면 해당 경로는 확장하지 않고 백엔드 확인 항목으로 기록한다.

Task R2.3 — Phase 번호 표시·구현 문서 정합성 복구

Goal 실행 P2-R3: Phase 2 완료 뒤 남은 사용자 표시와 plan의 Files·Progress를 실제 구현에 맞게 정렬한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-004, REV-P2-005

  • 시작 조건:

    • 보호 shell 빈 상태가 Character 연결 시점을 Phase 2로 표시하고, Phase 2 주요 Files와 하단 Progress가 실제 변경 경로·완료 기록과 다른 상태를 확인한다.
  • 완료 증거:

    • 빈 상태 문구와 관련 test가 Character 구현 Phase 3을 가리킴
    • Phase 2 주요 Files가 실제 수정·생성·test 경로와 일치함
    • 하단 검증 기록에 Phase 2 구현의 무엇을/왜/실제 명령·결과/남은 항목이 누적됨
    • 문서 test, App focused test와 git diff --check 실행 기록
  • 범위 밖:

    • Phase 3 Character 기능 구현
    • 기존 Phase 2 구현·Gate 기록 삭제 또는 덮어쓰기
  • 현재 Phase 2 문구와 Files·Progress 불일치를 test와 파일 존재 검사로 고정한다.

  • 사용자 표시를 Phase 3으로 바꾸고 Phase 2 주요 Files를 실제 경로로 갱신한다.

  • Phase 2 구현·Gate 검증을 하단 Progress에 기존 기록을 보존한 채 누적한다.

  • 문서/App focused test와 git diff --check를 실행한다.

  • 결과를 plan-task.md 검증 기록과 Phase 2 리뷰 문서에 누적한다.

P2-R3 수정 검증 기록 (2026-07-27):

  • RED: npm run test:run -- src/app/App.test.tsx src/shared/mocks/__tests__/mock-preview-docs.test.ts — Phase 3 placeholder와 실제 Phase 2 Files·Progress 기록이 없어 2 files 중 3 tests가 기대대로 실패했다.
  • GREEN focused: 같은 command — 2 files / 21 tests 통과.
  • Final Gate: 1차 npm run test:run — 34 files / 135 tests 통과. Reviewer blocker 보강 후 최종 npm run test:run — 34 files / 136 tests 통과. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, git diff --check 모두 성공.
  • Production guard: VITE_API_MODE=mock npm run build:prod — 기대대로 VITE_API_MODE=mock is only available during development 오류로 거부됐다.
  • LSP diagnostics: src/app directory 3 files / 0 diagnostics, src/shared/mocks directory 8 files / 0 diagnostics. App.tsx 단일 fresh diagnostics는 timeout이었고 directory diagnostics와 typecheck로 보완 확인했다.

create_goal objective 초안 — P2-R3

  • objective: P2-R3의 확정 review 항목 REV-P2-004와 REV-P2-005를 수정하고 Phase 번호 표시와 계획 문서 추적성을 복구한다. plan-task.md에 추가된 Task R2.3만 수행한다.
  • 완료 조건: 사용자 문구, 실제 Files, 하단 Progress, focused test와 문서 검증 기록이 서로 일치한다.
  • 금지 조건: Phase 3 기능 구현이나 기존 완료 기록 삭제·덮어쓰기를 수행하지 않는다.
  • 중단 조건: Phase 번호가 다시 변경되면 현재 기록을 지우지 않고 새 결정 기록을 먼저 추가한다.

Task R2.4 — 동일 세션 재진입 fail-closed 복구

Goal 실행 P2-R4: 최초 보호 route 검증 성공 뒤 같은 session으로 route를 이탈·재진입해도 새 probe 성공 전에는 보호 shell을 다시 열지 않는다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-006

  • 시작 조건:

    • 같은 AuthSessionRecord/ai-characters 성공 → /login 이탈 → /ai-characters 재진입 뒤 probe 실패 시 보호 shell이 노출되는 현재 동작을 실패 test로 재현한다.
  • 완료 증거:

    • 최초 성공 뒤 동일 session 재진입 pending·404·network·403에서 보호 shell 비노출
    • focused App test, server boundary E2E와 P2 Gate 실행 기록
  • 범위 밖:

    • 인증/session 저장 방식 전면 교체
    • Phase 3 Character 화면 구현
  • 같은 session route 이탈·재진입 뒤 pending·404 실패에서 보호 shell이 노출되는 실패 test를 추가한다.

  • route 이탈 또는 새 보호 route probe 시작 시 이전 검증 상태가 새 진입을 열지 못하게 최소 상태 전이를 구현한다.

  • focused App test와 server boundary E2E를 실행한다.

  • 결과를 plan-task.md 검증 기록과 Phase 2 리뷰 문서에 누적한다.

P2-R4 수정 검증 기록 (2026-07-27):

  • RED: npm run test:run -- src/app/App.test.tsx — 동일 session /login 이탈 후 /ai-characters 재진입 404에서 보호 main이 남아 1 test가 기대대로 실패했다.
  • GREEN focused: 같은 command — 1 file / 20 tests 통과. route visit key로 이전 보호 route 검증을 새 진입에 재사용하지 않게 했다.
  • Refactor guard: ProtectedAdminShellsrc/app/protected-admin-shell.tsx로 분리해 App.tsx를 153 pure LOC로 낮췄고, App focused test와 lint가 통과했다.

Task R2.5 — Auth fixture status·media type 계약 복구

Goal 실행 P2-R5: mock auth fixture가 API Contract의 비ADMIN status와 login media type 경계를 완화하지 않게 한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-007

  • 시작 조건:

    • 비ADMIN logout 401과 text/plain login 200을 실패 test로 재현한다.
  • 완료 증거:

    • 비ADMIN logout 403, invalid·revoked token logout 401 유지
    • login은 application/json만 허용하고 지원하지 않는 media type은 415 오류 envelope 반환
    • 정상 login/logout 회귀 없음
  • 범위 밖:

    • 제공되지 않은 login credential 정책과 회원 role 의미 확장
  • 비ADMIN logout 403과 non-JSON login 415 실패 test를 추가한다.

  • logout token access 분기와 login media type 검증을 계약 status에 맞춘다.

  • auth handler focused test와 mock auth preview 회귀를 실행한다.

  • 결과를 plan-task.md 검증 기록과 Phase 2 리뷰 문서에 누적한다.

P2-R5 수정 검증 기록 (2026-07-27):

  • RED: npm run test:run -- src/shared/mocks/__tests__/auth-handlers.test.ts — 비ADMIN logout이 401, text/plain login이 200으로 응답해 2 tests가 기대대로 실패했다.
  • GREEN focused: 같은 command — 1 file / 10 tests 통과. 비ADMIN logout은 403, invalid·revoked logout은 401 유지, unsupported media type login은 415로 고정했다.

Task R2.6 — 모든 mock route state의 지속 안내 복구

Goal 실행 P2-R6: mock mode의 login·성공 shell·403·보호 오류 상태에서 Mock Preview 안내를 지속 표시한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-008

  • 시작 조건:

    • mock mode 403과 404/network 보호 오류 화면에서 banner가 사라지는 현재 동작을 실패 test로 재현한다.
  • 완료 증거:

    • mock mode login·성공 shell·403·404/network에서 banner 표시
    • server mode에서는 banner 미표시 유지
    • 접근성·320px 회귀 없음
  • 범위 밖:

    • 오류 화면 디자인 개편
  • 403과 보호 오류 화면의 mock banner 실패 test를 추가한다.

  • Mock Preview 안내를 모든 mock route state의 공통 상위 경계에 한 번만 배치한다.

  • focused App/banner test와 mock preview E2E를 실행한다.

  • 결과를 plan-task.md 검증 기록과 Phase 2 리뷰 문서에 누적한다.

P2-R6 수정 검증 기록 (2026-07-27):

  • RED: npm run test:run -- src/app/App.test.tsx — mock mode access denied와 보호 route 오류 화면에서 Mock Preview status가 없어 2 tests가 기대대로 실패했다.
  • GREEN focused: 같은 command — 1 file / 22 tests 통과. login·성공 shell·403·보호 오류가 공통 RouteFrame의 banner를 사용한다.

Task R2.7 — Mode별 bare E2E 실행 경계 복구

Goal 실행 P2-R7: bare npm run e2enpm run e2e:mock이 각 mode에 유효한 spec만 수집·실행하게 한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-009

  • 시작 조건:

    • 두 bare E2E 명령의 --list가 같은 spec 집합을 수집하고 mock bare 실행이 실패하는 현재 동작을 실패 test로 재현한다.
  • 완료 증거:

    • bare npm run e2enpm run e2e:mock이 각 mode 유효 spec만 실행해 exit 0
    • 목록·실행 contract test와 scripts 문서가 일치
  • 범위 밖:

    • Playwright config 복제와 CI 전면 재구성
  • package script 또는 Playwright 설정의 mode별 spec 경계 실패 test를 추가한다.

  • 가장 작은 script/config 변경으로 server 전용·mock 전용 spec 수집을 분리한다.

  • --list, bare E2E 실행과 docs sync test를 실행한다.

  • 결과를 plan-task.md 검증 기록과 Phase 2 리뷰 문서에 누적한다.

P2-R7 수정 검증 기록 (2026-07-27):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts — bare e2e script가 mode별 spec 경계를 명시하지 않아 1 test가 기대대로 실패했다.
  • GREEN focused: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 5 tests 통과.
  • 목록 검증: npm run e2e -- --list — server 4 files / 32 tests, npm run e2e:mock -- --list — mock 2 files / 24 tests로 분리됐다.
  • Surface: npm run e2e — 32 tests 통과, npm run e2e:mock — 24 tests 통과.

P2-R4~P2-R7 통합 검증 기록 (2026-07-27):

  • npm run test:run -- src/app/App.test.tsx src/shared/mocks/__tests__/auth-handlers.test.ts src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 4 files / 37 tests 통과.
  • npm run test:run — 34 files / 141 tests 통과.
  • npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0.
  • VITE_API_MODE=mock npm run build:prod — 기대대로 VITE_API_MODE=mock is only available during development로 exit 1.
  • test ! -e dist/mockServiceWorker.js, git diff --check HEAD — 모두 exit 0.
  • LSP diagnostics: src/app directory 4 files / 0 diagnostics, src/shared/mocks directory 8 files / 0 diagnostics.

Task R2.8 — Phase 2 완료 문서 추적성 복구

Goal 실행 P2-R8: P2-R4~P2-R7 이후의 실제 변경 경로·검증 범위와 Phase 2 plan/review 메타데이터를 일치시킨다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-010
  • 시작 조건:
    • 3차 review 반영 전 git diff HEAD --name-only은 37개 경로인데 review 상단과 종료 판정은 36개로 기록된 상태였음을 확인한다.
    • Phase 2 주요 Filessrc/app/App.test.tsx, src/app/admin-pages.tsx, src/app/browser-location.ts, src/app/protected-admin-shell.tsx를 포함하지 않는 상태를 확인한다.
  • 완료 증거:
    • Phase 2 주요 FilesP2-R4~P2-R7까지의 실제 코드·test 경로와 일치함
    • review 대상·working tree 경로 수·종료 판정과 3차 재검증 기록이 현재 범위와 일치함
    • 완료된 회귀 Task의 Files·Interfaces와 검증 근거가 독립 실행자가 추측하지 않을 수준으로 기록됨
    • 문서 대체 검증과 git diff --check 결과가 plan/review에 누적됨
  • 범위 밖:
    • 애플리케이션 코드·test·설정 변경
    • 기존 완료 체크와 과거 검증 기록 삭제 또는 덮어쓰기

Files:

  • Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md
  • Modify: docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md
  • Test: 없음 — 이 Task는 완료 문서의 소유 경로와 검증 메타데이터만 정정한다.

Interfaces:

  • Consumes: git diff HEAD --name-only, P2-R4~P2-R7 구현·검증 기록, REV-P2-010
  • Produces: Phase 3 실행자와 reviewer가 사용할 최신 Phase 2 Files·review 범위·검증 기준

TDD 예외 사유: 실행 동작을 변경하지 않는 문서 정합성 수정이므로 실패 test를 추가하면 제품 동작과 무관한 문자열 고정 test만 늘어난다.

대체 검증 방법: 현재 변경 경로와 Phase 2 Files·review 범위를 직접 대조하고, review ID·goal 연결과 Markdown diff를 명령으로 검사한다.

  • Phase 2 주요 FilesP2-R4~P2-R7의 실제 코드·test 경로를 추가한다.
  • review 상단의 대상·working tree 기준과 종료 판정을 3차 재검증 범위에 맞춘다.
  • 완료된 회귀 Task의 Files·Interfaces·검증 근거 누락을 기존 기록을 보존한 채 보완한다.
  • 아래 대체 검증을 실행하고 실제 결과를 plan/review에 누적한다.

P2-R8 수정 검증 기록 (2026-07-27):

  • 대체 RED: git diff HEAD --name-only로 기존 tracked 변경 37개 경로를 확인했고, rg -n 'P2-R(10|[1-9])|REV-P2-0(0[1-9]|1[0-3])|주요 Files|기준 commit 또는 working tree' docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md에서 REV-P2-010P2-R8이 문서 추적성 미충족 상태로 남아 있음을 확인했다.
  • GREEN: Phase 2 주요 Filessrc/app/App.test.tsx, src/app/admin-pages.tsx, src/app/browser-location.ts, src/app/protected-admin-shell.tsx를 추가하고, review 상단·요약·종료 판정·수정 후 검증 기록을 P2-R8 완료와 P2-R9~P2-R10 잔여 상태로 정렬했다.
  • 검증: git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md — exit 0.

검증 기준:

  • 실행 명령: git diff HEAD --name-only, rg -n 'P2-R(10|[1-9])|REV-P2-0(0[1-9]|1[0-3])|주요 Files|기준 commit 또는 working tree' docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md, git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md
  • 기대 결과: 실제 변경 경로와 문서의 소유 Files·review 범위 차이 0건, review ID와 goal 연결 누락 0건, whitespace 오류 0건.
  • 수동 확인: review 상단·발견 요약·plan 전환·종료 판정·수정 후 검증 기록이 모두 P2-R1~P2-R10의 현재 상태를 같은 의미로 표시한다.

Task R2.9 — Mock Preview 모바일 메뉴의 반응형·inert 경계 복구

Goal 실행 P2-R9: Mock Preview 모바일 메뉴가 열린 상태에서도 banner를 배경 inert 경계에 포함하고 desktop breakpoint 전환 시 숨은 overlay와 inert 상태를 함께 해제한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-011, REV-P2-012
  • 시작 조건:
    • 320px에서 모바일 메뉴를 열면 Mock Preview banner의 closest('[inert]')null인 현재 동작을 browser 재현으로 확인한다.
    • 같은 상태에서 viewport를 1,200px로 넓히면 overlay는 display:none이지만 main의 inert ancestor와 aria-hidden=true가 남는 현재 동작을 browser 재현으로 확인한다.
  • 완료 증거:
    • 모바일 메뉴가 열린 동안 banner와 shell 본문이 같은 background inert·aria-hidden 경계에 포함됨
    • viewport가 lg 이상으로 바뀌면 mobile menu state와 inert·aria-hidden이 해제되고 desktop navigation·logout·main을 keyboard와 pointer로 사용할 수 있음
    • breakpoint 자동 종료 시 숨겨진 mobile trigger로 focus를 복귀하지 않음
    • App focused test, mock responsive E2E, 기존 accessibility shell E2E와 P2 Gate 실행 기록
  • 범위 밖:
    • Admin shell navigation 구조 전면 교체
    • Phase 3 Character UI와 새로운 breakpoint 체계 도입

Files:

  • Modify: src/app/App.tsx
  • Modify: src/app/protected-admin-shell.tsx
  • Test: src/app/App.test.tsx
  • Test: tests/e2e/mock-preview-shell.spec.ts

Interfaces:

  • Consumes: MockModeBanner({ apiMode }: { readonly apiMode: ApiMode }), ProtectedAdminShell({ routeError }: { readonly routeError: string | null }), Tailwind lg=1024px shell breakpoint
  • Produces: ProtectedAdminShell({ routeError, apiMode }: { readonly routeError: string | null; readonly apiMode: ApiMode })와 mobile overlay 표시 여부·background inert·aria-hidden이 항상 같은 상태인 composition

TDD 절차:

  • RED: 실패 테스트 작성/실패 확인src/app/App.test.tsx에 mock banner가 열린 menu의 inert background에 포함되는 test와 lg 전환 시 menu state가 닫히는 test를 추가하고, npm run test:run -- src/app/App.test.tsx src/shared/ui/__tests__/mock-mode-banner.test.tsx가 두 assertion에서 실패하는지 확인한다.
  • GREEN: 최소 구현/통과 확인 — banner와 shell을 하나의 background inert 경계로 조합하고 native viewport change에서 mobile state만 닫는 최소 구현으로 같은 명령을 통과시킨다.
  • REFACTOR: 정리/회귀 확인 — focus 복귀 조건과 breakpoint 이름을 정리한 뒤 App focused test, mock preview E2E, 기존 accessibility shell E2E와 P2 focused Gate를 실행한다.
  • TDD 단계와 아래 검증 기준의 실제 결과를 plan/review에 누적한다.

검증 기준:

  • 실행 명령: npm run test:run -- src/app/App.test.tsx src/shared/ui/__tests__/mock-mode-banner.test.tsx, npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx, npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts, npm run e2e -- tests/e2e/accessibility-shell.spec.ts, npm run typecheck, npm run lint
  • 기대 결과: focused unit 2 files / 26 tests 이상, P2 focused 7 files / 25 tests 이상, mock preview 4 projects / 24 tests 이상, accessibility shell 4 projects / 12 tests, type·lint 오류 0건; 1,024px·1,200px 전환 뒤 hidden mobile overlay와 inert background 잔존 0건.
  • 수동 확인: 320px에서 메뉴를 열면 banner와 본문이 보조기기 탐색에서 제외되고 menu만 탐색 가능하며, 열린 상태로 1,024px와 1,200px로 넓히면 desktop navigation·logout·main이 즉시 다시 동작한다.

Task R2.10 — 보호 route 오류의 fail-closed 재시도 복구

Goal 실행 P2-R10: 보호 route probe의 404·network 오류 화면에서 보호 shell을 열지 않은 채 사용자가 명시적으로 재시도할 수 있게 한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-013
  • 시작 조건:
    • P2-R9 완료.
    • 저장된 ADMIN session의 probe가 404 또는 network error로 실패하면 오류 alert만 있고 재시도·이동 control이 없는 현재 동작을 확인한다.
  • 완료 증거:
    • 404·network error 화면에 keyboard로 사용할 수 있는 명시적 재시도 control 제공
    • 재시도 중과 재실패 상태에서 보호 shell·navigation·logout 비노출 유지
    • 재시도 probe가 성공한 뒤에만 현재 session·route visit의 보호 shell 표시
    • server가 제공한 정상 오류 envelope의 한국어 message는 보존하고 network·invalid response에는 안전한 공통 안내 사용
    • App focused test, server boundary E2E와 P2 Gate 실행 기록
  • 범위 밖:
    • 자동 retry·자동 mock fallback
    • Phase 3 Character 목록 오류 UI와 API client 전면 교체

Files:

  • Modify: src/app/App.tsx
  • Test: src/app/App.test.tsx
  • Test: tests/e2e/server-mode-boundary.spec.ts

Interfaces:

  • Consumes: ProtectedRouteErrorPage({ message }: { readonly message: string }), BrowserLocationSnapshot.visitKey, ApiError.message
  • Produces: ProtectedRouteErrorPage({ message, onRetry }: { readonly message: string; readonly onRetry: () => void })와 수동 retry마다 새 probe attempt를 식별하는 App state

TDD 절차:

  • RED: 실패 테스트 작성/실패 확인src/app/App.test.tsx에 404 → retry 성공과 network → retry 실패의 fail-closed test를 추가하고 npm run test:run -- src/app/App.test.tsx가 retry control 부재로 두 test에서 실패하는지 확인한다.
  • GREEN: 최소 구현/통과 확인 — 오류 page에 retry button과 현재 session·route에 귀속된 새 probe attempt만 추가해 같은 명령을 통과시킨다.
  • REFACTOR: 정리/회귀 확인 — 오류 message·attempt state 이름을 정리한 뒤 App focused test, server boundary E2E, P2 focused Gate와 전체 unit을 실행한다.
  • TDD 단계와 아래 검증 기준의 실제 결과를 plan/review에 누적한다.

검증 기준:

  • 실행 명령: npm run test:run -- src/app/App.test.tsx, npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx, npm run test:run, npm run e2e -- tests/e2e/server-mode-boundary.spec.ts, npm run typecheck, npm run lint
  • 기대 결과: App 1 file / 26 tests 이상, P2 focused 7 files / 25 tests 이상, 전체 34 files / 145 tests 이상, server boundary 4 projects / 16 tests 이상, type·lint 오류 0건; retry 성공 전 보호 main·logout button 0건.
  • 수동 확인: 404와 offline 상태에서 retry button의 accessible name·focus indicator를 확인하고, 실패 중 shell이 보이지 않으며 연결 복구 후 한 번의 수동 retry로 shell이 열린다.

Task R2.11 — Phase 2 working tree 전체 경로 집계 복구

Goal 실행 P2-R11: tracked diff와 untracked 파일을 함께 집계해 Phase 2 review 범위와 완료 문서가 실제 working tree 전체를 누락 없이 표시하게 한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-014
  • 시작 조건:
    • P2-R8 완료.
    • git diff HEAD --name-only | wc -l은 37을 반환하지만 git status --porcelain=v1 | wc -l은 38을 반환하고 git ls-files --others --exclude-standardsrc/app/protected-admin-shell.tsx가 존재함을 확인한다.
  • 완료 증거:
    • review의 현재 working tree 범위를 tracked 37개와 untracked 1개를 포함한 38개 변경 항목으로 기록
    • P2-R8 대체 검증과 후속 reviewer 명령이 untracked 파일을 빠뜨리지 않는 git status --short --untracked-files=all 기준을 사용
    • plan/review의 현재 범위·종료 판정·검증 기록과 실제 working tree 항목 차이 0건
  • 범위 밖:
    • 애플리케이션 코드·test·설정 변경
    • 과거 시점의 37개 tracked diff 실행 결과 삭제 또는 덮어쓰기

Files:

  • Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md
  • Modify: docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md
  • Test: 없음 — working tree 집계 명령과 완료 문서 메타데이터만 정정한다.

Interfaces:

  • Consumes: git diff HEAD --name-only, git status --short --untracked-files=all, git ls-files --others --exclude-standard, REV-P2-014
  • Produces: tracked·untracked를 모두 포함하는 Phase 2 working tree 범위와 reviewer 검증 기준

TDD 예외 사유: 실행 동작을 변경하지 않는 문서·검증 명령 정정이므로 제품 test가 아닌 실제 Git 상태 대조가 실패·성공 증거다.

대체 검증 방법: tracked diff 수, 전체 status 항목 수와 untracked 목록을 각각 수집해 합계와 문서 범위를 대조한다.

  • P2-R8의 대체 검증 기준에 git status --short --untracked-files=all과 untracked 확인 명령을 추가한다.
  • review 상단·4차 검증·종료 판정의 현재 working tree 범위를 tracked 37개 + untracked 1개 = 38개 항목으로 정렬한다.
  • 과거 37개 tracked diff 기록은 당시 실행 결과로 보존하고 현재 전체 범위와 구분한다.
  • 아래 대체 검증과 문서 diff 검사를 실행하고 결과를 plan/review에 누적한다.

검증 기준:

  • 실행 명령: git diff HEAD --name-only | wc -l, git status --short --untracked-files=all | wc -l, git ls-files --others --exclude-standard, git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md
  • 기대 결과: 현재 기준 tracked diff 37개, untracked src/app/protected-admin-shell.tsx 1개, working tree 전체 38개 변경 항목, 현재 문서 범위 누락과 whitespace 오류 0건.
  • 수동 확인: review 상단·4차 검증·발견 요약·plan 전환·종료 판정이 P2-R1~P2-R13의 현재 상태와 38개 working tree 항목을 같은 의미로 표시한다.

Task R2.12 — 보호 route probe 대기 상태의 접근 가능한 피드백 복구

Goal 실행 P2-R12: 최초 보호 route 확인과 수동 retry가 진행되는 동안 보호 shell을 숨긴 채 사용자와 보조기기에 명시적인 loading 상태를 제공한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-015
  • 시작 조건:
    • P2-R10 완료.
    • retry 응답을 350ms 지연하면 #root child 0개, main·status·alert 0개이고 active element가 BODY인 현재 Chromium 동작을 확인한다.
  • 완료 증거:
    • 최초 probe와 retry pending 모두 RouteFrame 안에 지속적으로 보이는 한국어 loading 상태와 role="status" 제공
    • pending 동안 보호 main·navigation·logout 비노출과 mock mode banner 지속 표시 유지
    • 성공·404·network·401·403의 기존 fail-closed 분기와 수동 retry 동작 회귀 없음
    • App focused test, 지연된 server boundary E2E, P2 focused Gate와 전체 unit 실행 기록
  • 범위 밖:
    • 보호 shell skeleton 선노출
    • 자동 retry·자동 mock fallback과 전역 router 도입

Files:

  • Modify: src/app/App.tsx
  • Test: src/app/App.test.tsx
  • Test: tests/e2e/server-mode-boundary.spec.ts

Interfaces:

  • Consumes: PageState({ state: "loading", title, description }), RouteFrame({ apiMode, children }), ProtectedRouteVerification
  • Produces: 현재 session·route visit·retry attempt가 미검증인 동안 렌더되는 RouteFrame + accessible loading PageState

TDD 절차:

  • RED: 실패 테스트 작성/실패 확인src/app/App.test.tsx에 최초 probe pending 전용 test를 추가하고 기존 404 retry test에 retry pending assertion을 보강해 두 pending 시나리오가 role="status"를 제공하면서 보호 shell을 숨기는지 확인한다. npm run test:run -- src/app/App.test.tsx가 status 부재로 실패하는지 확인한다.
  • GREEN: 최소 구현/통과 확인 — 기존 PageState loading variant를 미검증 branch에 조합하는 최소 구현으로 같은 명령을 통과시킨다.
  • REFACTOR: 정리/회귀 확인 — loading copy와 branch 이름을 정리하고 기존 404 retry E2E에 350ms pending status assertion을 추가한 뒤 focused·전체 회귀를 실행한다.
  • TDD 단계와 아래 검증 기준의 실제 결과를 plan/review에 누적한다.

검증 기준:

  • 실행 명령: npm run test:run -- src/app/App.test.tsx, VITE_API_MODE=server npx playwright test tests/e2e/server-mode-boundary.spec.ts, npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx, npm run test:run, npm run typecheck, npm run lint
  • 기대 결과: App 1 file / 27 tests, server boundary 4 projects / 12 tests 이상, P2 focused 7 files / 25 tests 이상, 전체 34 files / 147 tests 이상, type·lint 오류 0건; 최초 pending 전용 test 1건과 기존 retry test 보강으로 두 pending 시나리오에서 visible role="status" 1건과 보호 main·logout 0건.
  • 수동 확인: 375px server mode에서 최초 진입과 retry 응답을 각각 350ms 이상 지연해 한국어 loading 안내가 보이고 빈 root가 발생하지 않으며, 성공 뒤에만 shell이 열린다.

Task R2.13 — Mode별 focused E2E 필터와 network retry 증거 복구

Goal 실행 P2-R13: bare mode 분리를 유지하면서 CLI spec 필터가 실제로 focused 실행되게 하고 network retry 실패를 server boundary E2E로 고정한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-016
  • 시작 조건:
    • P2-R12 완료.
    • npm run e2e -- --list tests/e2e/server-mode-boundary.spec.ts가 4 files / 32 tests를 수집하지만 직접 VITE_API_MODE=server npx playwright test tests/e2e/server-mode-boundary.spec.ts --list는 1 file / 12 tests만 수집함을 확인한다.
    • P2-R10 검증 기준은 server boundary 16 tests 이상을 요구하지만 현재 spec은 3 scenarios / 12 tests이고 network error test는 retry를 실행하지 않음을 확인한다.
  • 완료 증거:
    • bare npm run e2e는 server 4개 spec만, bare npm run e2e:mock은 mock 2개 spec만 실행
    • 두 npm script 뒤에 전달한 file filter가 고정 spec 목록과 합쳐지지 않고 해당 file만 수집
    • server boundary에 network error → 수동 retry → network 재실패의 retry 유지·shell 비노출·worker 0건 E2E 추가
    • mode boundary contract, focused list/실행, bare mode E2E, P2 Gate와 문서 동기화 기록
  • 범위 밖:
    • Playwright config 복제
    • project matrix·worker 수·CI 전체 구조 변경

Files:

  • Modify: package.json
  • Modify: playwright.config.ts
  • Modify: README.md
  • Modify: docs/agent-guide/scripts.md
  • Test: src/shared/mocks/__tests__/mode-boundary.test.ts
  • Test: src/shared/mocks/__tests__/mock-preview-docs.test.ts
  • Test: tests/e2e/server-mode-boundary.spec.ts

Interfaces:

  • Consumes: VITE_API_MODE=server | mock, Playwright testMatch, npm argument forwarding, 현재 server 4개·mock 2개 spec allowlist
  • Produces: file 목록을 내장하지 않는 e2e·e2e:mock scripts와 server testMatch=["**/server-mode-boundary.spec.ts", "**/smoke.spec.ts", "**/auth.spec.ts", "**/accessibility-shell.spec.ts"], mock testMatch=["**/mock-preview-shell.spec.ts", "**/mock-mode-boundary.spec.ts"]; CLI file filter와 mode allowlist의 교집합 실행 계약

TDD 절차:

  • RED: 실패 테스트 작성/실패 확인 — mode boundary test가 npm scripts의 고정 spec 목록 제거와 config의 mode별 exact allowlist를 요구하게 하고 npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts가 현재 script/config로 실패하는지 확인한다.
  • RED: surface 실패 확인 — network 재실패 retry E2E를 추가하고 현재 구현에서 동작을 확인하되, npm run e2e -- --list tests/e2e/server-mode-boundary.spec.ts가 불필요한 4 files를 수집해 focused list assertion을 실패시키는지 확인한다.
  • GREEN: 최소 구현/통과 확인 — script는 각각 VITE_API_MODE=server playwright test, VITE_API_MODE=mock playwright test만 유지하고 config의 apiModetestMatch에 Interfaces의 exact glob allowlist를 옮겨 contract·focused list·network retry E2E를 통과시킨다.
  • REFACTOR: 정리/회귀 확인 — mode allowlist 상수와 README/scripts 문구를 정리한 뒤 focused·bare E2E와 P2 Gate를 실행한다.
  • TDD 단계와 아래 검증 기준의 실제 결과를 plan/review에 누적한다.

검증 기준:

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts, npm run e2e -- --list tests/e2e/server-mode-boundary.spec.ts, npm run e2e:mock -- --list tests/e2e/mock-preview-shell.spec.ts, npm run e2e -- tests/e2e/server-mode-boundary.spec.ts, npm run e2e, npm run e2e:mock, npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx, npm run typecheck, npm run lint
  • 기대 결과: focused server boundary 1 file / 16 tests 이상, focused mock preview 1 file / 24 tests, bare server 4 files / 36 tests 이상, bare mock 2 files / 28 tests, P2 focused 7 files / 25 tests 이상, type·lint 오류 0건; network retry 재실패 뒤 retry button 1건, 보호 main·logout·mock worker 0건.
  • 수동 확인: 없음 — mode/file 수집 목록, network retry와 worker 경계는 Playwright list·실행 결과로 결정적으로 검증한다.

Task R2.14 — P2-R12 test 증거 정합성 복구

Goal 실행 P2-R14: P2-R12의 실제 test case 구조·실행 수를 plan/review의 완료 증거와 사실대로 정렬한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-017
  • 시작 조건:
    • P2-R11~P2-R13 구현·검증 완료.
    • rg -c '^test\(' src/app/App.test.tsx와 focused 실행이 모두 27 tests를 반환한다.
    • 최초 pending은 전용 test 1건, retry pending은 기존 404 retry test의 assertion 보강이지만 P2-R12 체크와 수정 기록은 두 신규 test·28 tests 이상으로 표시한다.
  • 완료 증거:
    • P2-R12의 현재 완료 증거가 최초 pending 전용 test 1건 추가 + 기존 retry test 보강 + App 27 tests로 일치
    • 이전 “두 신규 test·28 tests 이상” 표현과 정정 사유를 새 Progress에 보존하고 실제 실행 결과를 덮어쓰지 않음
    • plan/review 검색·diff 검사와 App focused test 결과를 수정 검증 기록에 누적
  • 범위 밖:
    • 애플리케이션 코드·test·설정 변경
    • 같은 retry pending 동작을 중복 검증하는 test case 추가
    • 기존 실행 이력 삭제 또는 제품 동작 완료 주장 변경

Files:

  • Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md
  • Modify: docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md
  • Test: 없음 — 구현·test 동작은 바꾸지 않고 완료 문서의 사실관계와 현재 상태만 정정한다.

Interfaces:

  • Consumes: REV-P2-017, src/app/App.test.tsx의 27 test declarations, P2-R12 완료 기록
  • Produces: actual test case와 검증 시나리오를 구분한 P2-R12 완료 증거와 정정 기록

TDD 예외 사유: 제품 코드와 test를 변경하지 않는 문서 정정이며, 새 test case를 추가하면 이미 존재하는 retry pending assertion을 중복하게 된다. 실제 test 선언·focused 결과와 문서 문자열 대조를 실패·성공 증거로 사용한다.

대체 검증 방법: App test 선언 수·두 pending 시나리오의 위치·focused 실행 수를 plan/review 문구와 대조하고, stale 표현 검색과 문서 diff 검사를 실행한다.

  • rg -c '^test\(' src/app/App.test.tsx와 두 pending test 위치를 수집해 27 tests 구조를 재확인한다.
  • P2-R12 체크·기대 결과·수정 검증 기록을 최초 전용 test 1건 + 기존 retry test 보강 + App 27 tests로 정정하고 이전 표현·정정 사유를 새 기록에 남긴다.
  • review 상단·발견 요약·종료 판정을 REV-P2-017 수정 완료와 P2-R14 완료 상태로 갱신한다.
  • 아래 대체 검증과 문서 diff 검사를 실행하고 실제 결과를 plan/review에 누적한다.

P2-R14 수정 검증 기록 (2026-07-27):

  • 대체 RED: rg -c '^test\(' src/app/App.test.tsx — 27건. rg -n 'initial protected route probe is pending|retries a protected route 404' src/app/App.test.tsx — 최초 pending 전용 test 1건과 기존 404 retry test 1건을 확인했다. 기존 P2-R12 기록은 두 신규 test·App 28 tests 이상으로 표시돼 실제 구조와 불일치했다.
  • GREEN: P2-R12 TDD 절차·기대 결과와 P2-R11~P2-R13 수정 검증 기록을 최초 pending 전용 test 1건 + 기존 retry test 보강 + App 27 tests로 정정했다. 이전 잘못된 표현과 정정 사유는 이 P2-R14 기록과 review REV-P2-017에 보존했다.
  • Focused: npm run test:run -- src/app/App.test.tsx — 1 file / 27 tests 통과.
  • 검증: rg -n '^[[:space:]]+- P2-R12 RED:.*신규 pending status tests 2건' docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md — no matches. git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md — exit 0.

검증 기준:

  • 실행 명령: rg -c '^test\(' src/app/App.test.tsx, rg -n 'initial protected route probe is pending|retries a protected route 404' src/app/App.test.tsx, npm run test:run -- src/app/App.test.tsx, rg -n '^[[:space:]]+- P2-R12 RED:.*신규 pending status tests 2건' docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md, git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md
  • 기대 결과: App test 선언·focused 실행 27건, 최초 pending 전용 test와 기존 retry test 각 1건 확인, 정정 대상인 잘못된 RED 기록 검색 결과 0건, 문서 whitespace 오류 0건.
  • 수동 확인: plan/review가 test case 수와 검증 시나리오 수를 구분하고 P2-R12의 실제 완료 증거를 같은 의미로 표시한다.

Task R2.15 — review plan 전환 절의 현재 수정 상태 정합성 복구

Goal 실행 P2-R15: Phase 2 review의 plan·goal 전환 절이 P2-R8~P2-R13의 현재 수정 완료 상태를 요약·상세·종료 판정과 일치하게 표시하도록 정정한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-018
  • 시작 조건:
    • review 요약·상세·종료 판정은 REV-P2-010REV-P2-016P2-R8P2-R13을 수정 완료로 표시한다.
    • 같은 review의 7. 확정 항목의 plan·goal 전환은 3차·4차 회귀 Task를 여전히 “아직 수정하지 않았다”고 표시하는 모순을 확인한다.
  • 완료 증거:
    • review §7의 P2-R8~P2-R13 전환 설명이 각 Task의 실제 수정 완료 상태와 일치함
    • 과거 시점의 발견·수정 전 검증 기록은 삭제하거나 현재 상태처럼 다시 쓰지 않고 그대로 보존함
    • stale 현재 상태 표현 검색 결과 0건과 문서 whitespace 오류 0건
  • 범위 밖:
    • 애플리케이션 코드·test·설정 변경
    • 과거 검증 기록과 당시의 남은 항목 삭제 또는 덮어쓰기
    • P2-R8~P2-R13 구현·검증의 재수행

Files:

  • Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md
  • Modify: docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md
  • Test: 없음 — 완료된 review의 현재 상태 문구만 정정하는 문서 Task다.

Interfaces:

  • Consumes: REV-P2-010REV-P2-016, P2-R8P2-R13 수정 검증 기록, review §5·§6·§8
  • Produces: review §7과 요약·상세·종료 판정이 같은 현재 상태를 표시하는 Phase 2 인수인계 문서

TDD 예외 사유: 제품 동작이나 실행 가능한 계약을 바꾸지 않는 문서 상태 정정이므로 새 제품 test를 추가하지 않는다.

대체 검증 방법: review §5·§6·§7·§8의 상태를 직접 대조하고 stale 현재 상태 문자열 검색과 Markdown diff 검사를 실행한다.

  • review §7의 3차 회귀 Task 설명을 P2-R8~P2-R10 수정 완료 상태로 정정한다.
  • review §7의 4차 회귀 Task 설명을 P2-R11~P2-R13 수정 완료 상태로 정정한다.
  • 과거 시점의 수정 전 기록과 남은 항목이 보존됐는지 확인한다.
  • 아래 대체 검증을 실행하고 실제 결과를 plan/review에 누적한다.

P2-R15 수정 검증 기록 (2026-07-27):

  • 대체 RED: rg -n '3차 재검증의 새 확정 4건.*아직 수정하지 않았다|4차 재검증의 새 확정 3건.*아직 수정하지 않았다' docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md — 2건. review §7이 완료된 P2-R8~P2-R13을 여전히 미수정 현재 상태로 표시했다.
  • GREEN: review §7의 3차·4차 회귀 Task 설명을 2026-07-27 수정·검증 완료 상태로 정정하고, review의 REV-P2-018 상태·발견 요약·종료 판정을 수정 완료로 맞췄다. §9의 당시 남은 항목과 과거 검증 기록은 보존했다.
  • 검증: 같은 stale 현재 상태 검색 — no matches. git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md — exit 0.

검증 기준:

  • 실행 명령: rg -n '3차 재검증의 새 확정 4건.*아직 수정하지 않았다|4차 재검증의 새 확정 3건.*아직 수정하지 않았다' docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md, git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md
  • 기대 결과: stale 현재 상태 검색 결과 0건, 문서 whitespace 오류 0건, review §5·§6·§7·§8의 P2-R8~P2-R13 상태 의미 차이 0건.
  • 수동 확인: review §9의 당시 남은 항목 기록은 이력으로 보존되고 §7의 현재 전환 상태만 수정 완료로 정정됐는지 확인한다.

Task R2.16 — Phase 2 현재 상태·하단 Progress 정합성 복구

Goal 실행 P2-R16: Phase 2 상단 현재 상태와 하단 최신 Progress가 P2-R15까지의 실제 수정 완료 및 Phase 3 진행 가능 상태를 일치하게 표시하도록 정정한다.

  • 연결 리뷰: Phase 2 리뷰REV-P2-019
  • 시작 조건:
    • P2-R15 Task 본문과 review는 수정 완료 및 열린 확정 항목 없음을 표시한다.
    • Phase 2 상단 현재 상태는 최초 추가 당시 설명만 유지하고, 하단 최신 Progress는 P2-R15 수정 필요 상태로 끝나는 모순을 확인한다.
  • 완료 증거:
    • Phase 2 상단 현재 상태가 P2-T1P2-T3, P2-GATE, P2-R1P2-R16 완료와 Phase 3 진행 가능 상태를 명시함
    • 6차 재검증의 당시 남은 항목은 이력으로 보존하고 하단에 P2-R16 수정 검증을 누적함
    • 최신 Progress, review 요약·종료 판정과 Phase 2 상단 상태의 의미 차이 0건
  • 범위 밖:
    • 애플리케이션 코드·test·설정 변경
    • 과거 검증 기록과 당시 남은 항목 삭제 또는 덮어쓰기
    • Phase 3 기능 구현

Files:

  • Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md
  • Modify: docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md
  • Test: 없음 — 완료된 Phase의 현재 상태와 누적 Progress만 정정하는 문서 Task다.

Interfaces:

  • Consumes: REV-P2-019, P2-R15 수정 검증, Phase 2 상단 현재 상태와 하단 Progress
  • Produces: Phase 3 시작 조건 판정에 사용할 일관된 Phase 2 완료 상태

TDD 예외 사유: 제품 동작이나 실행 가능한 계약을 바꾸지 않는 문서 상태 정정이므로 새 제품 test를 추가하지 않는다.

대체 검증 방법: 완료 상태 exact 문자열과 최신 Progress를 검색하고, 과거 6차 기록 보존 및 Markdown diff를 확인한다.

  • Phase 2 상단 현재 상태를 실제 완료 범위와 Phase 3 진행 가능 상태로 정정한다.
  • 6차 재검증의 당시 남은 항목을 보존하고 하단에 P2-R16 수정 검증을 누적한다.
  • review의 REV-P2-019 상태·plan 전환·종료 판정을 수정 완료로 갱신한다.
  • 아래 대체 검증과 Phase 2 전체 Gate를 실행하고 실제 결과를 plan/review에 누적한다.

P2-R16 수정 검증 기록 (2026-07-27):

  • 대체 RED: Phase 2 완료 상태 exact 검색과 tail 기반 최신 Progress 검색은 모두 no match, exit 1이었다. 상단은 최초 추가 설명에 머물고 문서 끝은 P2-R15 수정 필요 상태였다.
  • GREEN: Phase 2 상단에 P2-R16까지 완료 및 Phase 3 진행 가능 상태를 명시하고, 6차 당시 남은 항목은 보존한 채 하단에 P2-R16 수정 검증을 누적했다. review의 REV-P2-019와 종료 판정도 수정 완료로 정렬했다.
  • Phase 2 Gate: npm run test:run — 34 files / 147 tests 통과. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0, build는 각각 160 modules 변환. VITE_API_MODE=mock npm run build:prod — 기대한 guard로 exit 1.
  • E2E: npm run e2e — 4 projects / 36 tests 통과. npm run e2e:mock — 4 projects / 28 tests 통과.
  • Production: dist/mockServiceWorker.js 없음, production JS의 startMockWorker·mockServiceWorker.js 검색은 기대한 no-match exit 1.
  • 문서 검증: Phase 2 완료 상태와 최신 Progress exact 검색, 과거 6차 P2-R15 남은 항목 보존 검색, plan/review git diff --check를 통과했다.

검증 기준:

  • 실행 명령: Phase 2 완료 상태 exact 검색, tail 기반 최신 Progress 확인, 과거 P2-R15 남은 항목 보존 검색, git diff --check, Phase 2 전체 Gate
  • 기대 결과: 현재 상태·최신 Progress 의미 차이 0건, 과거 기록 보존, 문서 whitespace 오류 0건, Phase 2 자동 Gate 0 failure/0 error
  • 수동 확인: Phase 3 실행자가 과거 6차 기록을 열린 현재 Task로 오해하지 않고 P2-GATE와 모든 회귀 수정 완료를 확인할 수 있다.

계약 이력 안내 (2026-07-29): Phase 3~9 본문과 §7의 실행 기록은 OpenAPI 2.0.0 기준으로 수행된 당시 상태를 보존한다. 원작·장르 lookup, UTC 전송, Community pagination, FanTalk 수정·삭제, Comments와 active-only 계약의 최신 실행 기준은 OpenAPI 2.3.0과 Phase 10이다. 과거 Phase의 완료 체크를 다시 열거나 과거 RED/GREEN 증거를 현재 계약 증거로 재해석하지 않는다.

Phase 3. Character workspace vertical slice

목표: ADMIN이 Character를 검색·생성·선택하고 workspace에서 상세·수정·soft delete 요청과 후속 목록 재조회를 완료한다.

Phase Goal P3: Task 3.1 → 3.4와 Phase 3 Gate로 Character workspace vertical slice를 완성한다.

  • 시작 조건: P2-GATE 완료.
  • 완료 조건: P3-T1~P3-T4, P3-GATE 완료. 외부 의존은 구현 또는 명시적 제외 결정으로 종결.
  • 실행 순서: 계약 확인 → 목록/workspace → mutation → 반응형·접근성.

요구사항: CHAR-001~018, FILE-001~002, FILE-008~010, FILE-012, PRD 7, 9의 Character 범위.

외부 의존: EXT-007/CHAR-012searchTerm 지정 결과에 대한 active-only 보장. EXT-001/CHAR-013 original work lookup은 legacy 후보 GET /admin/chat/original/search?searchTerm={검색어}로 구현 계획에 포함한다. searchTerm 생략 시 활성 목록은 OpenAPI에 명시돼 있다. originalWorkId 미선택은 key 생략과 null이 모두 가능하며 frontend canonical serializer만 하나로 고정한다.

주요 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: 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

Phase 3 Task 실행 계약

Goal Files Interfaces TDD·검증 기준
P3-T1 Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md; Read: docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json; Test: 없음 Consumes: OpenAPI Character* schema·4 operation. Produces: CharacterListResponse, CharacterDetailResponse, create/update multipart와 screen inventory TDD 예외: 외부 계약 조사 Task다. node -e "JSON.parse(require('fs').readFileSync('docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json','utf8'))"와 `rg -n 'CHAR-0(1[2-8]
P3-T2 Create: src/features/characters/api/character-api.ts, src/features/characters/model/types.ts, src/features/characters/pages/{CharacterListPage,CharacterDetailPage}.tsx, src/features/characters/components/{CharacterList,CharacterListItem,CharacterProfile}.tsx, src/features/characters/tests/character-list.test.tsx, src/layouts/CharacterWorkspaceLayout.tsx, src/layouts/CharacterWorkspaceLayout.test.tsx; Modify: src/app/router.tsx, src/app/route-paths.ts Consumes: CharacterListResponse, CharacterDetailResponse. Produces: getCharacters({searchTerm,page,size}), getCharacter(characterId), list/workspace route TDD 적용: npm run test:run -- src/features/characters/tests/character-list.test.tsx src/layouts/CharacterWorkspaceLayout.test.tsx; 기대 exit 0. 수동 확인: searchTerm request와 deep link/read-only 상태.
P3-T3 Create: src/features/characters/schemas/character-schema.ts, src/features/characters/validation/character-image-policy.ts, src/features/characters/pages/CharacterFormPage.tsx, src/features/characters/components/{CharacterForm,CharacterImageField,OriginalWorkCombobox}.tsx, src/features/characters/tests/{character-api.test.ts,character-form.test.tsx}; Modify: src/features/characters/api/character-api.ts Consumes: Character create/update multipart, legacy original work lookup GET /admin/chat/original/search?searchTerm={검색어}. Produces: createCharacter, updateCharacter, deactivateCharacter, searchOriginalWorks, form serializer TDD 적용: npm run test:run -- src/features/characters/tests/character-api.test.ts src/features/characters/tests/character-form.test.tsx; 기대 exit 0. 수동 확인: 필수 image/systemPrompt, 원작 검색 선택, crop, 저장·목록 이동.
P3-T4 Modify: src/features/characters/pages/{CharacterListPage,CharacterDetailPage,CharacterFormPage}.tsx, src/features/characters/components/{CharacterList,CharacterListItem,CharacterProfile,CharacterForm,CharacterImageField}.tsx; Test: tests/e2e/character-workspace.spec.ts Consumes: P3-T2/T3 UI. Produces: viewport·keyboard capability evidence TDD 적용: npm run e2e:mock -- tests/e2e/character-workspace.spec.ts; 기대 지원 project 전부 통과. 수동 확인: 320/768/1280px, 200% zoom, keyboard, axe.

P3-T2~P3-T4는 각 row의 test에 가장 작은 실패 assertion을 먼저 추가해 RED를 확인하고, 최소 구현으로 같은 명령을 통과시킨 뒤 관련 feature test·typecheck·lint를 실행한다. 각 Task 마지막에는 RED, GREEN, REFACTOR와 수동 확인의 실제 결과를 §7 검증 기록에 누적한다.

Task 3.1 Phase 계약 확인

Goal 실행 P3-T1: Character 구현 계약, mock scenario와 화면 component map을 확정한다.

  • 시작 조건: P2-GATE 완료, PRD CHAR-001~018, MOCK-001~009와 OpenAPI Character 4개 operation/schema 확인.

  • 완료 증거: 체크박스 전체, 제공 계약 또는 제외 결정의 세 문서 일치, 상태/action inventory.

  • 범위 밖: 계약을 추정한 production adapter와 Character 화면 구현.

  • OpenAPI에서 목록 searchTerm/page/size, data.totalCount/content, item id, 상세 characterUUID/originalWork, mutation data=null을 contract fixture로 고정한다.

  • original work lookup endpoint, DTO, search/page를 확인하고, 미제공이면 network control을 제외한다. originalWorkId 미선택 serializer는 허용된 omit 또는 null 중 하나를 contract test로 고정한다.

  • OpenAPI 공통 400/401/403/404/405/406/415/500과 ApiErrorResponse를 fixture에 기록한다. Character 전용 message key는 미제공으로 표시하고 분기하지 않는다.

  • 목록·상세·form·workspace의 상태/action inventory를 작성하고 Page는 route/query/policy 조합, feature component는 Character 표시·입력 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.

  • 제공 계약 범위의 Character seed, loading·empty·error·success, legacy original work lookup과 CRUD/deactivate browser handler 시나리오를 확정한다.

P3-T1 확정 기록:

  • OpenAPI 제공 범위: GET/POST /api/v2/admin/ai-characters, GET/PUT /api/v2/admin/ai-characters/{characterId}만 Character operation으로 사용한다. 목록은 query searchTerm, page, size와 응답 data.totalCount, data.content[], item id를 fixture 기준으로 삼는다. 상세는 characterUUID, originalWork를 포함한다. 생성·수정 성공은 NullSuccessResponsedata=null만 신뢰하고 새 ID나 상세 DTO를 추정하지 않는다. multipart part는 create image, request, update optional image, required request만 사용한다.
  • Original work lookup: OpenAPI에는 없지만 legacy 후보 GET /admin/chat/original/search?searchTerm={검색어}를 사용한다. P3-T3에서 response DTO를 contract test로 고정하고 OriginalWorkCombobox network control을 만든다. originalWorkId 미선택 serializer는 P3-T3 contract test에서 key 생략을 canonical로 고정한다.
  • 오류 fixture: Character 4개 operation의 공통 오류는 OpenAPI ApiErrorResponse shape, 즉 success=false, message, data=null, errorProperty만 사용한다. 400/401/403/404/405/406/415/500 status fixture를 공통 오류로 둔다. OpenAPI 밖의 Character 전용 message key는 분기하지 않고 미정의 오류는 기본 문구로 처리한다.
  • 상태·action inventory: list는 searchTerm, page, size, items, totalCount, selectedCharacterId, loading, empty, error, retry; detail은 characterId, profile, isActive, readOnly, loading, error, refresh; form은 mode, fields, image, dirty, submitting, fieldErrors, submit, cancel, deactivate; workspace는 route characterId, active tab, breadcrumb, read-only policy, reload action을 가진다.
  • component map: Page는 route param, URL query, data loading, navigation, read-only policy를 조합한다. feature component는 Character 목록 표시, 상세 표시, form 입력·직렬화, image 선택 규칙만 맡는다. shared component는 Phase 1의 PageState, AppLayout, Button, form primitive, dialog contract를 재사용한다.
  • mock scenario: deterministic seed는 활성 Character 2건과 비활성 상세 1건, originalWork가 있는 상세 1건과 null 상세 1건을 둔다. handler는 list/detail loading, empty, common error, success, legacy original work lookup, create/update/deactivate 성공 및 공통 오류를 제공한다. create/update/deactivate 후 in-memory store의 list/detail을 OpenAPI 응답 shape로 갱신한다.

Task 3.2 목록·선택·workspace

Goal 실행 P3-T2: Character 목록·검색·선택과 URL 기반 workspace 복원을 완성한다.

  • 시작 조건: P3-T1 완료.

  • 완료 증거: 체크박스 전체, route/list/workspace test와 read-only/error 상태 검증 기록.

  • 범위 밖: Character 생성·수정·비활성화 form.

  • Character 목록·생성 path에는 characterId가 없고 하위 resource route에만 선택한 characterId가 들어가는 contract test를 작성한다.

  • 목록 UI의 search 상태를 API searchTerm으로 직렬화하고 page, size URL query 보존, data.content 역직렬화와 loading·empty·error·retry test를 작성한다.

  • 목록 request에 isActive·activeStatus가 없고 client-side 활성 filter도 없는 contract test를 작성한다. active-only 보장은 외부 의존으로 남긴다.

  • 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가 추정하지 않는다.

  • browser MSW handler와 deterministic seed로 목록 → 선택 → workspace deep link의 최종 UI를 dev:mock에서 확인한다.

Task 3.3 생성·수정·soft delete

Goal 실행 P3-T3: Character multipart 생성·수정·soft delete와 image/original-work 규칙을 완성한다.

  • 시작 조건: P3-T1, P3-T2 완료.

  • 완료 증거: 체크박스 전체, serializer/form/image/deactivate test, OQ-009 결정과 검증 기록.

  • 범위 밖: 하위 도메인 mutation. v2 original-work lookup이 별도 제공되지 않으면 legacy GET /admin/chat/original/search?searchTerm={검색어}를 사용한다.

  • create multipart가 필수 image와 필수 request JSON part를 보내고 request에 name, systemPrompt, description을 포함하며 isActive, externalCharacterId를 포함하지 않는 test를 작성한다.

  • 일반 update는 isActive를 생략하고 soft delete만 isActive=false를 보내며 true를 보내지 않는 test를 작성한다.

  • create-only region을 수정 화면에서 읽기 전용으로 표시하고 update payload에 보내지 않는 test를 작성한다.

  • name·systemPrompt·description visible label, field error, 중복 제출 방지, dirty-form 이탈 확인을 test한다.

  • OpenAPI optional scalar와 tags·hobbies·values·goals·relationships·personalities·backgrounds·memories 반복 입력을 create/update schema에 맞게 직렬화하는 test를 작성한다.

  • Character image의 JPEG/PNG·10MB, 1:1, 최대 800×800, no-upscale, crop 이동·zoom·reset·preview·취소·적용·keyboard 대안을 test한다.

  • crop 취소·교체 파일 미선택이 기존 image를 유지하고 기존 image 제거 UI는 없음을 test한다.

  • Phase 10 P10-T1에서 v2 searchAiCharacterOriginalWorks 기반 원작 검색 Combobox와 originalWorkId 선택 payload를 contract test로 고정한다. 미선택 payload는 canonical 생략으로 고정하고 legacy lookup은 제거한다.

  • 상세의 characterUUID는 읽기 전용으로 표시할 수 있지만 externalCharacterId로 이름을 바꾸지 않는다. 계약에 없는 creator member ID·nickname DTO/UI는 만들지 않고 creator 생성·동기화도 client가 수행하지 않는다.

  • create의 data=null 성공 후 목록 route로 이동해 목록을 재조회하고, update는 기존 characterId 상세 route로 재진입해 상세를 재조회한다. 현재 Character 화면은 React Query cache가 아니라 route 재진입 직접 요청으로 동기화한다.

  • 비활성화 AlertDialog가 영향·복원 미지원·hard delete 미지원을 설명하는 test를 작성한다.

  • soft delete 성공 후 목록 route 이동과 목록 재조회, 성공 toast를 확인하고 상세에 머물지 않는다. 비활성 항목이 서버 결과에서 제외되는지는 active-only 계약 제공 후 server mode에서 검증한다.

  • 초기 Character form을 실제 페이지에서 확인한 뒤 name, systemPrompt, description과 tags·hobbies·values·goals·relationships·personalities·backgrounds·memories의 최대 길이·개수 권고안을 작성한다. backend 호환 확인 전에는 상한을 구현하지 않는다.

  • mock store가 create/update/deactivate 후 목록·상세를 같은 server response contract로 갱신하는 E2E를 작성한다.

Task 3.4 Character 반응형·접근성

Goal 실행 P3-T4: Character viewport capability와 keyboard·zoom·axe 기준을 검증한다.

  • 시작 조건: P3-T2, P3-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 3 Gate

Goal 실행 P3-GATE: Character mock UI journey와 실제 server integration 상태를 분리해 판정한다.

  • 시작 조건: P3-T1~P3-T4 완료.
  • 완료 증거: 아래 명령과 Expected 통과, 외부 의존의 구현/제외 상태 기록.
  • 범위 밖: 실패와 무관한 Audio 기능.
npm run test:run -- src/features/characters src/layouts/CharacterWorkspaceLayout.test.tsx
npm run e2e:mock -- tests/e2e/character-workspace.spec.ts
npm run e2e
npm run typecheck
npm run lint
npm run build

Expected: mock mode에서 login → Character 검색/생성 → 선택/workspace → 수정 → soft delete 요청 → 목록 재조회 UI가 OpenAPI request/response shape로 통과한다. server mode 결과는 별도로 기록하며 active-only·original work 계약이 없으면 fixture로 보장을 추정하지 않고 연동 대기로 남긴다.

수동 확인: 320/768/1280px와 200% zoom에서 검색·workspace·필수 image 생성·수정·비활성화 Dialog를 keyboard-only로 확인하고, searchTerm, multipart part와 mutation 후 network 요청이 OpenAPI와 일치하는지 본다.

Task R3.1 — Character optional form 계약 완성

Goal 실행 P3-R1: Character create/update form에서 확정된 optional scalar와 반복 배열을 편집·직렬화한다.

  • 연결 리뷰: Phase 3 재리뷰REV-P3-001

  • 시작 조건: OpenAPI CharacterCreateRequest/CharacterUpdateRequestCHAR-003, CHAR-018.

  • Files: Modify: src/features/characters/pages/CharacterCreatePage.tsx, src/features/characters/pages/CharacterEditPage.tsx, src/features/characters/api/character-api.ts, src/features/characters/tests/CharacterCreatePage.test.tsx, src/features/characters/tests/CharacterEditPage.test.tsx, src/features/characters/tests/character-api.test.ts, tests/e2e/character-workspace.spec.ts.

  • Interfaces: scalar age/gender/mbti/speechPattern/speechStyle/appearance/region/originalTitle/originalLink/characterType; arrays tags/hobbies/values/goals/relationships/personalities/backgrounds/memories; update region은 읽기 전용.

  • 범위 밖: 계약에 없는 최대 길이·배열 개수, creator field, 새로운 form framework 도입.

  • RED: create/update에서 각 scalar·array 대표값과 null/empty canonical payload를 검증하는 form/API test를 실패시킨다.

  • GREEN: 기존 두 Page 안에서 section별 최소 field/row editor를 추가하고 현재 multipart serializer로 전송한다.

  • REFACTOR: 반복 row helper는 실제 8개 소비만 공유하고 dirty/clear semantics를 정리한다.

  • 실행 명령: npm run test:run -- src/features/characters; npm run e2e:mock -- tests/e2e/character-workspace.spec.ts.

  • 기대 결과: exit 0, OpenAPI property 누락 0건, update region request 0건, 생성 필수 field 회귀 0건.

  • 수동 확인: desktop/tablet create/edit에서 모든 section 입력·추가·삭제와 저장 후 재조회 값을 확인한다.

Progress — 2026-07-30 P3-R1 완료

  • RED: npm run test:run -- src/features/characters/tests/CharacterCreatePage.test.tsx src/features/characters/tests/CharacterEditPage.test.tsx에서 신규 3개 test가 나이, 원작 제목 등 누락 label 때문에 실패하는 것을 확인했다.
  • GREEN: CharacterOptionalFields와 optional field serialization helper를 추가하고 create/edit page에서 optional scalar·반복 배열을 multipart request에 직렬화했다. npm run test:run -- src/features/characters/tests/CharacterCreatePage.test.tsx src/features/characters/tests/CharacterEditPage.test.tsx src/features/characters/tests/character-api.test.ts 결과 3 files / 18 tests passed.
  • REFACTOR/회귀: npm run test:run -- src/features/characters 결과 6 files / 26 tests passed. npm run e2e:mock -- tests/e2e/character-workspace.spec.ts 결과 41 passed / 3 skipped. optional 입력 추가로 keyboard-only E2E의 tab 한도만 80회로 조정했다. npm run typecheck, npm run lint, npm run build 모두 exit 0.
  • 정정: 독립 리뷰에서 수정 상세 응답에 없는 originalTitle/originalLink를 일반 저장 시 null로 전송할 수 있는 blocker를 확인했다. RED npm run test:run -- src/features/characters/tests/CharacterEditPage.test.tsx는 2 failed로 해당 전송을 재현했고, touched flag를 추가해 사용자가 입력·삭제한 경우에만 update payload에 포함하도록 수정했다. 이후 npm run test:run -- src/features/characters/tests/CharacterCreatePage.test.tsx src/features/characters/tests/CharacterEditPage.test.tsx src/features/characters/tests/character-api.test.ts는 3 files / 18 tests passed, npm run test:run -- src/features/characters는 6 files / 26 tests passed, npm run e2e:mock -- tests/e2e/character-workspace.spec.ts는 41 passed / 3 skipped, npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다.

Task R3.2 — Character form 실패 복구와 image format 검증

Goal 실행 P3-R2: Character mutation 실패 뒤 입력을 유지해 재시도하고 JPEG/PNG extension↔MIME pair를 제출 전에 검증한다.

  • 연결 리뷰: Phase 3 재리뷰REV-P3-002, REV-P3-003

  • 시작 조건: P3-R1 form 구조 또는 충돌 없는 현재 form, FILE-002, 공통 ApiError.

  • Files: Create: src/features/characters/validation/character-image-policy.ts; Modify: CharacterCreatePage.tsx, CharacterEditPage.tsx, 해당 page test.

  • Interfaces: .jpg/.jpeg ↔ image/jpeg, .png ↔ image/png, 10,485,760 bytes; form error는 server message를 보존하고 finally로 pending을 해제한다.

  • 범위 밖: backend image sniffing, 공통 crop 계산(P1-R7).

  • RED: create/update rejection 뒤 button 재활성·오류 표시·두 번째 성공과 양방향 MIME mismatch 거부 test를 작성한다.

  • GREEN: form-level try/catch/finally와 Character image policy를 적용한다.

  • REFACTOR: create/edit 중복 validation·error copy만 작게 공유하고 Character 전체 회귀를 실행한다.

  • 실행 명령: npm run test:run -- src/features/characters; npm run typecheck; npm run lint.

  • 기대 결과: exit 0, 실패 뒤 재시도 1회 성공, mismatch multipart 0건.

  • 수동 확인: server 500 후 입력 유지·재시도와 잘못된 filename/MIME inline 오류를 확인한다.

Progress — 2026-07-30 P3-R2 완료

  • RED: npm run test:run -- src/features/characters/tests/CharacterCreatePage.test.tsx src/features/characters/tests/CharacterEditPage.test.tsx 결과 2 files failed, 6 failed / 13 passed, 2 unhandled ApiError였다. 신규 retry test는 서버 메시지를 찾지 못했고, mismatch test는 JPEG 또는 PNG 파일만 업로드하세요. inline 오류가 없어 실패했다.
  • GREEN: src/features/characters/validation/character-image-policy.ts를 추가해 .jpg/.jpeg ↔ image/jpeg, .png ↔ image/png, 10MB 제한을 검증하고, create/edit page에서 file 선택 시 crop source 전에 inline 오류를 표시했다. mutation submit은 ApiError message를 form 오류로 표시하고 finally에서 submit 상태를 해제한다. npm run test:run -- src/features/characters/tests/CharacterCreatePage.test.tsx src/features/characters/tests/CharacterEditPage.test.tsx src/features/characters/tests/character-api.test.ts 결과 3 files / 24 tests passed.
  • REFACTOR/회귀: npm run test:run -- src/features/characters 결과 6 files / 32 tests passed. npm run e2e:mock -- tests/e2e/character-workspace.spec.ts 병렬 실행은 axe-heavy direct route test가 30초 제한에 걸렸고, focused Chromium 재실행은 1 passed였다. 해당 test에만 60초 timeout을 부여한 뒤 npm run e2e:mock -- tests/e2e/character-workspace.spec.ts --workers=1 결과 41 passed / 3 skipped였다. npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. LSP diagnostics도 변경 TS/TSX와 E2E 파일에서 오류 없음이었다.

Task R3.3 — 워크스페이스 탭 keyboard semantics와 운영 문구 복구

Goal 실행 P3-R3: 캐릭터 워크스페이스의 탭/내비게이션 의미와 keyboard 이동을 일치시키고 내부 Task 문구를 운영자 안내에서 제거한다.

  • 연결 리뷰: Phase 3 현재 리뷰REV-P3-004, REV-P3-005

  • 시작 조건: PRD 7.2, 14.2의 1차 탭·keyboard-only 이동과 비활성 read-only 안내.

  • 완료 증거: 실패하는 keyboard/accessible-role test, 최소 수정, Character focused unit·mock E2E·접근성 Gate와 검증 기록.

  • Files: Modify: src/layouts/CharacterWorkspaceLayout.tsx, src/layouts/CharacterWorkspaceLayout.test.tsx, tests/e2e/character-workspace.spec.ts, 필요 시 tests/e2e/accessibility.spec.ts.

  • Interfaces: ARIA tab pattern을 유지하면 활성 tab만 page Tab 순서에 두고 좌우 화살표·tabpanel 연결을 구현한다. 페이지 내비게이션으로 단순화하면 nav/link와 aria-current="page"를 사용해 tab 역할을 제거한다.

  • 범위 밖: route 구조 변경, 새 router dependency, 비활성 Character 정책 변경.

  • RED: 현재 다섯 role=tab이 모두 Tab 순서에 들어가고 좌우 화살표로 이동하지 않는 실패 test, 배너에 P3-T2가 노출되는 실패 test를 작성한다.

  • GREEN: 한 가지 일관된 접근성 패턴만 적용하고 배너를 운영 영향·복원 미지원 중심의 한국어 문구로 교체한다.

  • REFACTOR: active 상태와 panel 연결 중복을 작은 data structure로 정리하되 범용 tab abstraction은 만들지 않는다.

  • 실행 명령: npm run test:run -- src/layouts src/features/characters; npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/accessibility.spec.ts; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 선언한 role과 keyboard 동작 불일치 0건, 사용자 화면의 P3-T2 노출 0건.

  • 수동 확인: keyboard-only로 프로필 → 오디오 → 시리즈 → 커뮤니티 → FanTalk 이동과 비활성 배너 문구를 확인한다.

Progress — 2026-07-30 P3-R3 완료

  • RED: npm run test:run -- src/layouts/CharacterWorkspaceLayout.test.tsx 결과 1 file failed, 2 failed / 3 passed. 신규 native navigation expectation은 캐릭터 워크스페이스 내비게이션 role을 찾지 못하고 기존 tablist/tab을 노출해 실패했으며, 비활성 배너는 P3-T2 내부 문구를 계속 표시해 운영 문구 assertion이 실패했다.
  • GREEN: CharacterWorkspaceLayout의 workspace 전환 UI를 nav + native link + aria-current="page" 패턴으로 단일화하고, active link data structure로 중복을 줄였다. 비활성 배너는 조회 전용 운영 영향, 복원 기능 미지원, 영구 삭제와 삭제 후 복구 미제공을 설명하는 한국어 문구로 교체했다. npm run test:run -- src/layouts/CharacterWorkspaceLayout.test.tsx 결과 1 file / 5 tests passed.
  • REFACTOR/회귀: npm run test:run -- src/features/characters src/layouts/CharacterWorkspaceLayout.test.tsx src/features/community-posts/tests/community-list.test.tsx src/features/fan-talks/tests/fan-talk-list.test.tsx 결과 9 files / 45 tests passed. npm run e2e:mock -- tests/e2e/character-workspace.spec.ts --project=chromium 결과 11 passed. npm run typechecknpm run lint는 모두 exit 0이었다. 변경 TS/TSX 파일 LSP diagnostics는 모두 오류 0건이었다.

Task R3.4 — 워크스페이스 header characterId 복구

Goal 실행 P3-R4: 모든 캐릭터 워크스페이스 header에 현재 numeric characterId를 다시 표시해 운영자가 작업 대상을 식별할 수 있게 한다.

  • 연결 리뷰: Phase 3 현재 리뷰REV-P3-006

  • 시작 조건: PRD §7.2의 이미지·이름·활성 상태·characterId 상시 표시 기준.

  • 완료 증거: 현재 ID 미노출 assertion을 뒤집은 실패 test, header 최소 표시 수정, Character/layout focused unit·mock E2E·접근성 Gate 통과.

  • Files: Modify: src/layouts/CharacterWorkspaceLayout.tsx, src/layouts/CharacterWorkspaceLayout.test.tsx, tests/e2e/character-workspace.spec.ts; Docs: P3-R4 수정 후 검증 기록.

  • Interfaces: API의 numeric character.id를 운영 label과 함께 표시하고 characterUUID 프로필 필드는 별도 값으로 유지한다.

  • 범위 밖: route ID 형식 변경, UUID를 characterId로 대체, 전체 상세 metadata 재배치.

  • RED: active/inactive workspace header 모두 현재 characterId를 표시해야 한다는 실패 test를 작성하고 기존 미노출 assertion을 제거했다.

  • GREEN: 이름·상태 주변에 구분되는 characterId label/value를 표시했다.

  • REFACTOR: Series visual QA의 raw 내부 field-name 검색은 요구사항상 표시해야 하는 header ID를 예외 처리하고 실제 원치 않는 raw label 검사는 유지했다.

  • 실행 명령: npm run test:run -- src/layouts/CharacterWorkspaceLayout.test.tsx src/features/characters; npm run e2e:mock -- tests/e2e/character-workspace.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 모든 workspace header의 현재 numeric ID 표시, 다른 캐릭터 ID 혼입 0건.

  • 수동 확인: Character 101/202의 프로필·Audio·Series·Community·FanTalk header에서 각각 올바른 ID를 확인한다.

P3-R4 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/layouts/CharacterWorkspaceLayout.test.tsx — 2 failed / 3 passed. active 101과 inactive 202 workspace header에서 characterId: 표시를 찾지 못해 기대대로 실패했다.
  • GREEN focused: npm run test:run -- src/layouts/CharacterWorkspaceLayout.test.tsx src/features/characters — 성공, 7 files / 41 tests passed. header의 이름·상태 근처에 numeric characterId가 표시되고 기존 프로필 characterUUID는 별도 값으로 유지됨을 확인했다.
  • REFACTOR: rg -n 'characterId:|raw|SUN|PROCEEDING|genreId' tests/e2e src/features/series src/layouts로 자동 raw-leak assertion을 확인했고, 현재 Series raw label 검사는 characterId:를 금지하지 않아 추가 변경하지 않았다.
  • 개발 중 E2E: 사용자 지시에 따라 전체 Task 완료 전 반복 실행하지 않았다. 지정 mock Character E2E는 최종 회귀 단계에서 필요 시 실행한다.

Task R3.5 — Character URL query 계약 키 정렬

Goal 실행 P3-R5: Character 목록의 브라우저 검색 상태를 API 계약과 같은 searchTerm query로 읽고 쓰며 직접 링크 복원을 보장한다.

  • 연결 리뷰: Phase 3 현재 리뷰REV-P3-007

  • 시작 조건: PRD §7.1과 OpenAPI GET /api/v2/admin/ai-characterssearchTerm, page, size query 계약.

  • 완료 증거: ?searchTerm=루나 직접 진입과 검색 변경 URL assertion의 실패 test, 목록 URL parser/serializer 최소 수정, focused unit·mock E2E·정적 Gate 통과.

  • Files: Modify: src/features/characters/pages/CharacterListPage.tsx, src/features/characters/tests/character-list.test.tsx, tests/e2e/character-workspace.spec.ts.

  • Interfaces: 브라우저 URL과 API request 모두 searchTerm을 사용하고 page, size, 300ms 검색 동작은 유지한다.

  • 범위 밖: legacy search query의 장기 호환 layer, router dependency 도입, server-side filter 추가.

  • RED: ?searchTerm=루나&page=1&size=20 직접 진입이 input·request를 복원하고 검색 변경 URL에 search가 없어야 한다는 실패 test를 작성했다.

  • GREEN: URL read/write key를 searchTerm으로 교체하고 adapter 인자를 그대로 연결했다.

  • REFACTOR: 내부 state 이름은 필요한 범위에서만 정리하고 별도 query abstraction을 만들지 않았다.

  • 실행 명령: npm run test:run -- src/features/characters/tests/character-list.test.tsx; npm run e2e:mock -- tests/e2e/character-workspace.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, Character URL의 검색 key는 searchTerm 하나이고 직접 링크·검색·pagination 왕복이 일치.

  • 수동 확인: 검색 결과 2페이지 URL을 새 탭에서 열어 같은 검색어·page·size와 목록을 복원하는지 확인한다.

P3-R5 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/features/characters/tests/character-list.test.tsx — 2 failed. 핵심 실패는 ?searchTerm=루나&page=1&size=20 직접 진입 뒤 API request의 searchTermnull인 점이었다. 두 번째 loading flow는 같은 parser 불일치로 초기 대기 Promise가 열리지 않아 timeout됐다.
  • GREEN focused: npm run test:run -- src/features/characters/tests/character-list.test.tsx — 성공, 1 file / 2 tests passed. 직접 링크 input·request 복원과 검색 변경 URL의 searchTerm 사용, search= 미사용을 확인했다.
  • REFACTOR/회귀: npm run test:run -- src/features/characters — 성공, 6 files / 36 tests passed. npm run typecheck, npm run lint, npm run build, git diff --check — 모두 exit 0 또는 no output. LSP diagnostics: CharacterListPage.tsx, character-list.test.tsx 오류 0건.
  • 개발 중 E2E: 사용자 지시에 따라 전체 Task 완료 전 반복 실행하지 않았다. 지정 mock Character E2E는 최종 회귀 단계에서 필요 시 실행한다.

Task R3.6 — Character image crop 확정 경계 보강

Goal 실행 P3-R6: Character 생성·수정에서 원본 파일을 crop 적용 전에 mutation payload로 확정하지 않고 준비·취소·연속 선택 경계를 안전하게 처리한다.

  • 연결 리뷰: Phase 3 현재 리뷰REV-P3-008

  • 시작 조건: FILE-007, FILE-009~010, PRD §10.5 저장 중 feedback와 Character image 필수/optional 계약.

  • 완료 증거: 원본 제출·crop 취소·준비 중 제출·stale selection을 재현하는 실패 test, staged selection 최소 수정, Character focused unit·mock E2E·file Gate 통과.

  • Files: Modify: src/features/characters/pages/CharacterCreatePage.tsx, src/features/characters/pages/CharacterEditPage.tsx, 관련 Character tests와 tests/e2e/character-workspace.spec.ts; 필요 시 기존 Character image helper.

  • Interfaces: committed image는 crop onApply 결과만 가리킨다. 준비/crop 중 제출은 비활성화·guard하고, 취소는 생성의 새 선택을 제거하며 수정의 기존 서버 media를 유지한다. 마지막 선택만 dialog를 열고 준비 실패를 inline 표시한다.

  • 범위 밖: 공통 crop dialog 계산 변경, 새 범용 upload state library, backend image sniffing.

  • RED: create의 crop 미적용 원본 제출과 취소 뒤 제출, create/edit의 pending source 준비 중 submit, 느린 첫 선택이 빠른 둘째 선택을 덮는 경우를 실패 test로 고정했다.

  • GREEN: raw file과 committed crop result를 분리하고 selection token·준비 상태·오류 처리·submit guard를 추가했다.

  • REFACTOR: 새 전역 abstraction 없이 create/edit 내부의 작은 중복 상태로 유지하고, 기존 Character App 회귀 test는 crop apply 경로를 지나도록 정렬했다.

  • 실행 명령: npm run test:run -- src/features/characters; npm run e2e:mock -- tests/e2e/character-workspace.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, Character multipart의 uncropped 원본 0건, 취소·실패·stale source의 mutation 0건, 정상 crop 결과만 전송.

  • 수동 확인: 느린 이미지에서 연속 선택·취소·적용·즉시 제출을 반복해 선택한 마지막 crop 결과만 preview와 request에 반영되는지 확인한다.

P3-R6 수정 검증 기록 — 2026-07-31

  • RED: npm run test:run -- src/features/characters/tests/CharacterCreatePage.test.tsx src/features/characters/tests/CharacterEditPage.test.tsx — 기대 실패 확인, 2 files failed / 6 failed / 23 passed 및 reject 준비 실패 unhandled rejection을 확인했다. 핵심 실패는 생성 pending submit button 미비활성, 생성 crop cancel 뒤 raw file 잔존, 수정 pending submit button 미비활성, 준비 reject inline 오류 미표시였다.
  • GREEN: CharacterCreatePage.tsxCharacterEditPage.tsx에서 committed image를 crop apply 결과만 보유하도록 분리하고, selection id·준비 상태·stale ignore·reject inline 오류·submit disabled/guard를 추가했다. npm run test:run -- src/features/characters/tests/CharacterCreatePage.test.tsx src/features/characters/tests/CharacterEditPage.test.tsx — 성공, 2 files / 29 tests passed.
  • Character 회귀: npm run test:run -- src/features/characters — 성공, 6 files / 43 tests passed. character-mutation-reload.test.tsx는 App 생성 성공 경로가 실제 crop dialog 적용 후 저장하도록 보강했다.
  • 정적 검증: npm run typecheck, npm run lint, npm run build, git diff --check — 모두 성공(exit 0). Build는 255 modules transformed, production bundle 생성 완료.
  • LSP: lsp_diagnosticsCharacterCreatePage.tsx, CharacterEditPage.tsx, character-mutation-reload.test.tsx에서 오류 0건이었다. CharacterCreatePage.test.tsx, CharacterEditPage.test.tsx 단일 fresh diagnostics는 timeout이었고, lsp_diagnostics src/features/charactersnpm run typecheck로 characters 디렉터리 오류 0건을 보완 확인했다.
  • E2E: 사용자 지시가 “개발 중 unit tests만, 전체 Task 이후 필요 시 E2E”였으므로 npm run e2e:mock -- tests/e2e/character-workspace.spec.ts --project=chromium은 이번 P3-R6 개발 중 실행하지 않고 후속 최종 회귀 단계로 이연했다.

Phase 4. Audio vertical slice

목표: 선택 Character의 Audio를 제목 검색·검수·발행·수정·비활성화하고 대용량 upload를 안전하게 제어한다.

Phase Goal P4: Task 4.1 → 4.4와 Phase 4 Gate로 Audio 조회·재생·발행·upload slice를 완성한다.

  • 시작 조건: Phase 3 workspace core(P3-T2)와 P2-GATE 완료.
  • 완료 조건: P4-T1~P4-T4, P4-GATE 완료. 오류·price 계약은 제공값 또는 명시된 최소 규칙으로 종결.
  • 실행 순서: 계약 확인 → 목록/상세/player → form/upload → 반응형·접근성.

요구사항: AUDIO-001~033, FILE-001~002, FILE-006~009, FILE-012~013, PRD 9의 Audio 범위.

외부 의존: EXT-007 active-only 반환 보장. price 상한은 99999이고, 파일 용량·MIME 등 backend 동일 검증은 확인됐다. OpenAPI 밖의 Audio 도메인 오류 message key는 분기하지 않는다. status filter·답변 없는 status badge는 현재 범위에서 제외한다.

주요 Files:

  • Create: src/features/audio-contents/api/{audio-content-api,audio-content-theme-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

Phase 4 Task 실행 계약

Goal Files Interfaces TDD·검증 기준
P4-T1 Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md; Read: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json; Test: 없음 Consumes: AudioContent*, AudioTheme* schema·5 Audio operation. Produces: list/detail/create/update/theme contract map과 screen inventory TDD 예외: 외부 계약 조사 Task다. OpenAPI JSON parse와 `rg -n 'AUDIO-0(2[7-9]
P4-T2 Create: src/features/audio-contents/api/audio-content-api.ts, src/features/audio-contents/model/types.ts, src/features/audio-contents/pages/{AudioContentListPage,AudioContentDetailPage}.tsx, src/features/audio-contents/components/{AudioContentList,AudioContentListItem,AudioContentSummary}.tsx, src/features/audio-contents/tests/{audio-list,audio-player}.test.tsx Consumes: AudioContentListResponse, AudioContentDetailResponse. Produces: getAudioContents({characterId,search_word,page,size}), getAudioContent({characterId,contentId,timezone})와 player UI TDD 적용: npm run test:run -- src/features/audio-contents/tests/audio-list.test.tsx src/features/audio-contents/tests/audio-player.test.tsx; 기대 exit 0. 수동 확인: 2자 검색, timezone, 단일 재생·no-auto-refetch.
P4-T3 Create: src/features/audio-contents/api/{audio-content-theme-api,upload-audio-content}.ts, src/features/audio-contents/schemas/audio-content-schema.ts, src/features/audio-contents/validation/audio-cover-policy.ts, src/features/audio-contents/pages/AudioContentFormPage.tsx, src/features/audio-contents/components/{AudioContentForm,AudioContentThemeSelect,ReleaseScheduleField}.tsx, src/features/audio-contents/tests/{audio-contract.test.ts,audio-upload.test.ts,audio-form.test.tsx}; Modify: src/features/audio-contents/api/audio-content-api.ts Consumes: AudioContentCreateRequest, AudioContentUpdateRequest, AudioContentTheme. Produces: createAudioContent, updateAudioContent, deactivateAudioContent, upload adapter TDD 적용: npm run test:run -- src/features/audio-contents/tests/audio-contract.test.ts src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-form.test.tsx; 기대 exit 0. 수동 확인: contentFile, local releaseDate, theme, 진행률·취소·재시도.
P4-T4 Modify: src/features/audio-contents/pages/{AudioContentListPage,AudioContentDetailPage,AudioContentFormPage}.tsx, src/features/audio-contents/components/{AudioContentList,AudioContentListItem,AudioContentSummary,AudioContentForm,AudioContentThemeSelect,ReleaseScheduleField}.tsx; Test: tests/e2e/audio-content.spec.ts Consumes: P4-T2/T3 UI. Produces: viewport·keyboard capability evidence TDD 적용: npm run e2e:mock -- tests/e2e/audio-content.spec.ts; 기대 지원 project 전부 통과. 수동 확인: 320px player, 200% zoom, keyboard, axe.

P4-T2~P4-T4는 각 row의 focused test로 RED → GREEN → REFACTOR를 실행하고, 관련 feature test·typecheck·lint 결과와 수동 확인을 §7 검증 기록에 누적한다.

Task 4.1 Phase 계약 확인

Goal 실행 P4-T1: Audio 오류·price·theme·상태 계약, mock scenario와 component map을 확정한다.

  • 시작 조건: P3-T2, P2-GATE 완료, PRD AUDIO-001~033, MOCK-001~009와 OpenAPI Audio·theme operation/schema 확인.

  • 완료 증거: 체크박스 전체, contract fixture와 상태/action inventory의 세 문서 일치.

  • 범위 밖: 오류 status/key 또는 price 상한 추정과 Audio UI 구현.

  • OpenAPI 공통 오류 status·shape를 fixture에 기록하고 Audio 전용 message key와 backend container·codec 오류 계약은 미제공으로 표시한다. 정확한 fixture를 추정하지 않는다.

  • 오디오 테마 목록 GET /api/v2/admin/ai-characters/audio-content-themes가 query/body 없이 호출되고 data[]id, theme, image를 반환하는 contract fixture를 기록한다.

  • price 최대값이 제공되면 schema와 경계 test를 추가하고, 없으면 상한을 만들지 않는다.

  • 목록 search_word/page/size, data.totalCount/items, 상세 필수 timezone, 생성 contentFile/coverImage/request, 생성 data.contentId, 수정 data=null을 contract fixture로 고정한다.

  • status query·status field가 없음을 고정하고 client status filter·status enum을 만들지 않는다.

  • 목록·상세·player·form/upload의 상태/action inventory를 작성하고 Page는 route/query/policy 조합, feature component는 Audio 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.

  • 제공 계약 범위의 Audio seed와 목록·상세·player·theme·upload CRUD browser handler 시나리오를 확정한다.

Task 4.2 목록·상세·player

Goal 실행 P4-T2: Audio 목록·상세 복원과 안전한 단일 재생 흐름을 완성한다.

  • 시작 조건: P4-T1 완료.

  • 완료 증거: 체크박스 전체, list/detail/player test와 signed URL 비기록·no-auto-refetch 기록.

  • 범위 밖: 생성·수정·upload form.

  • UI 검색어가 2자 이상일 때만 API search_word로 직렬화되고 page, size URL 상태와 data.items 역직렬화, loading·empty·error·retry가 동작하는 test를 작성한다.

  • status query·활성 query·client-side status/active filter request가 0회임을 test한다.

  • Audio detail route의 직접 진입과 새로고침에서 timezone=Asia/Seoul을 보내 같은 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를 작성한다.

  • mock mode는 실제 endpoint shape와 local preview audio asset으로 목록 → 상세 → 단일 재생 최종 UI를 확인한다.

Task 4.3 발행 form·upload

Goal 실행 P4-T3: theme 선택, 즉시/예약 발행, multipart media와 진행률·취소·재시도를 완성한다.

  • 시작 조건: P4-T1, P4-T2 완료.

  • 완료 증거: 체크박스 전체, contract/form/upload/file-boundary test, OQ-009 결정과 검증 기록.

  • 범위 밖: resumable upload, client codec 판정, 계약 없는 price 상한.

  • 생성 multipart의 contentFile, coverImage, request가 필수이고 수정에는 optional coverImagerequest만 있으며 content file 교체 part·UI가 없음을 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를 작성한다.

  • 즉시 공개 기본값은 날짜 입력을 비활성화·초기화하고 releaseDate=null, timezone="Asia/Seoul"을 보낸다.

  • 예약 공개는 미래 Asia/Seoul 시각만 받고 yyyy-MM-dd HH:mm 문자열과 timezone="Asia/Seoul"을 보내며 UTC Z로 변환하지 않는 test를 작성한다.

  • 수정 form은 계약에 없는 release schedule·theme·series·content file 변경 control을 만들지 않고 기존 값을 읽기 전용으로 표시한다.

  • create request에 필수 title, detail, tags, price, 유효한 themeId가 있고 status, isActive, seriesIds가 없음을 contract test로 고정한다.

  • create optional purchase/limited/adult/preview/point/comment/detail/language field의 enum·type·OpenAPI default와 serializer를 test한다.

  • update request는 title, detail, tags, price, isAdult, isActive, isPointAvailable, isCommentAvailable 이외 field를 보내지 않고 soft delete에만 isActive=false를 보낸다.

  • upload 진행률, AbortController 취소, 전체 재시도, 실패 후 form/file 상태 보존을 test한다.

  • 415 server 오류를 field 안내로 보존하고 resumable upload는 만들지 않는다.

  • create 성공의 data.contentId로 상세에 이동하고, update/soft delete의 data=null 성공은 기존 ID cache를 무효화한다. soft delete 후 목록 이동과 toast를 제공하며 active-only 제거는 외부 계약 제공 후 server mode에서 검증한다.

  • 초기 Audio form을 실제 페이지에서 확인한 뒤 title, detail, tags의 최대 길이 권고안을 작성한다. backend 호환 확인 전에는 상한을 구현하지 않는다.

  • mock handler가 multipart request contract를 검증하고 create/update/deactivate 후 같은 store의 list/detail을 갱신하는 E2E를 작성한다.

Task 4.4 Audio 반응형·접근성

Goal 실행 P4-T4: Audio viewport capability와 player/form 접근성 기준을 검증한다.

  • 시작 조건: P4-T2, P4-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 4 Gate

Goal 실행 P4-GATE: Audio mock UI journey와 실제 server integration 상태를 분리해 판정한다.

  • 시작 조건: P4-T1~P4-T4 완료.
  • 완료 증거: 아래 명령과 Expected 통과 및 Phase 검증 기록.
  • 범위 밖: 실패와 무관한 Series/Community 구현.
npm run test:run -- src/features/audio-contents src/shared/ui src/shared/validation
npm run e2e:mock -- tests/e2e/audio-content.spec.ts
npm run e2e -- tests/e2e/audio-content.spec.ts
npm run typecheck
npm run lint
npm run build

Expected: mock mode에서 OpenAPI contentFile·releaseDate 계약으로 Audio 즉시/예약 생성 → 진행률/취소/재시도 → 검색·상세 재생 → 허용 field 수정 → soft delete 요청의 UI가 통과하고 media error가 자동 refetch·자동 재생을 0회 발생시킨다. status filter·seriesIds·content file 교체 request는 0건이며 server mode와 active-only 결과는 별도로 기록한다.

수동 확인: 320px player와 desktop/tablet form을 keyboard-only로 확인하고, search_word, timezone, contentFile, releaseDate, theme field와 수정 금지 control이 실제 network 요청·화면에 일치하는지 본다.

Task R4.1 — Audio create 설정과 raw price validation 완성

Goal 실행 P4-R1: Audio create optional 설정을 form에서 편집하고 음수·소수 가격을 변형하지 않고 거부한다.

  • 연결 리뷰: Phase 4 재리뷰REV-P4-001, REV-P4-002

  • 시작 조건: AUDIO-019, AUDIO-033, OpenAPI AudioContentCreateRequest.

  • Files: Modify: AudioContentForm.tsx, audio-content-form-helpers.ts, audio-content-schema.ts, audio-form.test.tsx, audio-contract.test.ts, tests/e2e/audio-content.spec.ts.

  • Interfaces: purchaseOption/limited/isAdult/isGeneratePreview/isOnlyRental/isPointAvailable/isCommentAvailable/isFullDetailVisible/previewStartTime/previewEndTime/languageCode; raw price는 0..99999 integer만 serialize.

  • 범위 밖: OpenAPI 밖 상관관계 validation, update에서 계약상 수정 불가한 field.

  • RED: optional control별 대표 payload와 -1, 1.5 request 0건 test를 작성한다.

  • GREEN: contract default로 초기화한 visible control과 raw/display price state를 최소 구현한다.

  • REFACTOR: request builder에 hard-coded field를 제거하고 create schema/form/E2E를 재실행한다.

  • 실행 명령: npm run test:run -- src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-contract.test.ts; npm run e2e:mock -- tests/e2e/audio-content.spec.ts.

  • 기대 결과: exit 0, optional property 입력 누락 0건, -1/1.5/100000 request 0건, 0/99999 허용.

  • 수동 확인: desktop/tablet에서 설정별 label·keyboard 입력과 가격 오류 focus를 확인한다.

  • 완료 기록(2026-07-30): create 설정 control·payload와 raw price invalid 상태를 구현하고 REV-P4-001, REV-P4-002를 수정 완료 처리했다. npm run test:run -- src/features/audio-contents 8 files / 35 tests passed, npm run e2e:mock -- tests/e2e/audio-content.spec.ts 21 passed / 3 skipped, npm run typecheck, npm run lint, npm run build, targeted git diff --check 통과.

Task R4.2 — upload 중복 제출과 401 만료 복구

Goal 실행 P4-R2: Audio upload pending 동안 POST를 한 번만 실행하고 XHR 401을 공통 session expiry 흐름으로 처리한다.

  • 연결 리뷰: Phase 4 재리뷰REV-P4-003, REV-P4-004

  • 시작 조건: P1-R6의 인증 만료 interface 또는 동일 callback contract 확정.

  • Files: Modify: AudioContentForm.tsx, upload-audio-content.ts, audio-form-upload.test.tsx, audio-upload.test.ts, AudioContentFormPage.tsx, app API dependency 조합.

  • Interfaces: upload adapter는 token reader와 onAuthExpired를 주입받고 protected 401 burst에서 session clear/login 1회; form은 pending ref와 disabled 상태를 가진다.

  • 범위 밖: resumable upload, refresh token, Axios migration.

  • RED: unresolved upload에 submit 두 번 시 POST 2건인 실패와 XHR malformed/normal 401에서 session이 남는 실패 test를 추가한다.

  • GREEN: synchronous pending guard/disabled button과 공통 auth expiry dependency를 적용한다.

  • REFACTOR: abort/error/success 모든 종료 경로에서 pending/controller cleanup을 한곳으로 정리한다.

  • 실행 명령: npm run test:run -- src/features/audio-contents/tests/audio-form-upload.test.tsx src/features/audio-contents/tests/audio-upload.test.ts src/shared/api/__tests__/client-auth.test.ts.

  • 기대 결과: exit 0, pending 중 upload request 1건, 401 clear/login 1회, cancel 후 재제출 가능.

  • 수동 확인: 느린 upload double-click과 만료 token upload의 login 복귀를 확인한다.

  • 완료 기록(2026-07-30): pending upload 중 submit 2회가 upload request 1건으로 고정되고 cancel 후 재제출 가능하도록 form guard를 추가했다. XHR upload adapter는 token reader/session clear/login callback을 주입받아 protected 401 burst에서 clear/login 1회만 실행한다. RED 확인: form은 expected 2 to be 1, upload는 clearSession 0회로 실패. GREEN/검증: focused 2 files / 19 tests passed, Audio 전체 8 files / 38 tests passed, npm run typecheck, npm run lint, npm run build, targeted git diff --check 통과.

Task R4.3 — Audio media accept와 cover format pair 정렬

Goal 실행 P4-R3: validator와 file picker가 같은 M4A MIME 집합을 사용하고 cover extension↔MIME 불일치를 거부한다.

  • 연결 리뷰: Phase 4 재리뷰REV-P4-005, REV-P4-006

  • 시작 조건: AUDIO-014, AUDIO-024, FILE-002.

  • Files: Modify: AudioContentForm.tsx, audio-content-form-helpers.ts, audio-cover-policy.ts, audio-contract.test.ts, file-media-policy.test.ts.

  • Interfaces: input accept는 AUDIO_FILE_POLICY.allowedMimeTypes; cover mapping은 jpg/jpeg→image/jpeg, png→image/png.

  • 범위 밖: container/codec sniffing, backend 오류 key.

  • RED: accept에 audio/mp4 포함과 cover 양방향 mismatch 거부 test를 작성한다.

  • GREEN: shared audio policy에서 accept를 생성하고 cover pair map을 적용한다.

  • REFACTOR: 중복 MIME literal을 제거하고 Community audio 회귀를 실행한다.

  • 실행 명령: npm run test:run -- src/shared/validation/file-media-policy.test.ts src/features/audio-contents src/features/community-posts.

  • 기대 결과: exit 0, canonical/compat M4A 2종 허용, mismatch cover 0건.

  • 수동 확인: Chrome/Safari file chooser에서 .m4a 선택 가능 여부를 확인한다.

  • 완료 기록(2026-07-30): Audio file picker acceptAUDIO_FILE_POLICY.allowedMimeTypes에서 생성하고, cover .jpg/.jpeg→image/jpeg, .png→image/png mismatch를 upload 전 mime 오류로 차단했다. RED 확인: accept는 audio/mp4 누락으로 실패, cover mismatch는 { ok: true }로 실패. GREEN/검증: focused 3 files / 24 tests passed, Audio+Community 회귀 12 files / 76 tests passed, npm run typecheck, npm run lint, npm run build, targeted git diff --check 통과.

Task R4.4 — Audio 일반 update와 soft delete schema 분리

Goal 실행 P4-R4: Audio 일반 update에서 isActive를 금지하고 soft delete adapter만 { isActive:false }를 전송하게 한다.

  • 연결 리뷰: Phase 4 현재 리뷰REV-P4-007

  • 시작 조건: AUDIO-007, OpenAPI AudioContentUpdateRequest, 기존 deactivateAudioContent.

  • 완료 증거: isActive=true/null 실패 test, schema/API 최소 수정, Audio focused unit·mock E2E·Phase Gate와 검증 기록.

  • Files: Modify: src/features/audio-contents/schemas/audio-content-schema.ts, src/features/audio-contents/api/audio-content-api.ts, src/features/audio-contents/tests/audio-contract.test.ts, src/shared/mocks/audio-content-mock-store.ts.

  • Interfaces: 일반 AudioContentUpdateRequest에는 isActive가 없고, 별도 deactivate schema는 z.strictObject({isActive:z.literal(false)})만 허용한다.

  • 범위 밖: OpenAPI JSON 축소, Audio 복원, backend partial update 의미 변경.

  • RED: 일반 update schema/body가 isActive=true/null/false를 거부하고 deactivate body만 false를 허용하는 실패 test를 작성한다.

  • GREEN: 일반 update와 deactivate request schema/API helper/mock parser를 분리한다.

  • REFACTOR: 기존 update test의 금지된 isActive:true 성공 assertion을 제거하고 Community·Series와 같은 불변식 용어로 정리한다.

  • 실행 명령: npm run test:run -- src/features/audio-contents src/shared/mocks; npm run e2e:mock -- tests/e2e/audio-content.spec.ts; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 일반 Audio update의 isActive request 0건, deactivate false 이외 parse 0건.

  • 수동 확인: Audio 수정 저장과 비활성화 network multipart의 request part를 각각 확인한다.

Progress — 2026-07-30 P4-R4 완료

  • RED: npm run test:run -- src/features/audio-contents/tests/audio-contract.test.ts에서 normal update의 isActive:false가 거부되지 않아 실패하는 것을 확인했다.
  • GREEN: audioContentUpdateRequestSchema에서 isActive를 제거하고 audioContentDeactivateRequestSchemaz.strictObject({ isActive: z.literal(false) })로 분리했다. deactivateAudioContent는 별도 deactivate body helper를 사용하고, mock parser/store도 normal update와 deactivate 경로를 분리했다. npm run test:run -- src/features/audio-contents/tests/audio-contract.test.ts 결과 1 file / 10 tests passed였다.
  • REFACTOR/회귀: Audio MSW handler를 src/shared/mocks/audio-content-handlers.ts로 추출해 handlers.ts를 238 lines로 낮췄고, npm run test:run -- src/features/audio-contents src/shared/mocks 결과 14 files / 67 tests passed였다. npm run e2e:mock -- tests/e2e/audio-content.spec.ts 결과 21 passed / 3 skipped였고, skipped 3건은 기존 keyboard-only platform policy다. npm run typecheck, npm run lint, npm run build 모두 exit 0이었다. 변경 TS 파일 LSP diagnostics는 오류 0건이었고, git diff --check도 출력 없이 통과했다.

Task R4.5 — Audio URL query 계약 키 정렬

Goal 실행 P4-R5: Audio 목록의 브라우저 검색 상태를 API 계약과 같은 search_word query로 읽고 쓰며 직접 링크 복원을 보장한다.

  • 연결 리뷰: Phase 4 현재 리뷰REV-P4-008

  • 시작 조건: PRD §7.1과 OpenAPI Audio 목록의 search_word, page, size query 계약.

  • 완료 증거: ?search_word=루나 직접 진입과 검색 변경 URL assertion의 실패 test, URL parser/serializer 최소 수정, Audio focused unit·mock E2E·정적 Gate 통과.

  • Files: Modify: src/features/audio-contents/pages/AudioContentListPage.tsx, src/features/audio-contents/tests/audio-list.test.tsx, tests/e2e/audio-content.spec.ts.

  • Interfaces: 브라우저 URL과 API request 모두 search_word를 사용하고 2자 이상 검색, page, size 동작은 유지한다.

  • 범위 밖: legacy search query 호환 layer, 검색 최소 글자 수 변경, router dependency 도입.

  • RED: ?search_word=루나&page=1&size=20 직접 진입이 input·request를 복원하고 검색 변경 URL에 search가 없어야 한다는 실패 test를 작성한다.

  • GREEN: URL read/write key를 search_word로 교체한다.

  • REFACTOR: Character와 query helper를 성급히 공유하지 않고 Audio page 내부 변경으로 끝낸다.

  • 실행 명령: npm run test:run -- src/features/audio-contents/tests/audio-list.test.tsx; npm run e2e:mock -- tests/e2e/audio-content.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, Audio URL의 검색 key는 search_word 하나이고 직접 링크·검색·pagination 왕복이 일치.

  • 수동 확인: 검색 결과 URL을 새로고침·새 탭 진입해 검색어와 목록이 유지되는지 확인한다.

Progress — 2026-07-31 P4-R5 완료

  • RED: npm run test:run -- src/features/audio-contents/tests/audio-list.test.tsx에서 ?search_word=루나&page=1&size=20 직접 진입 후 검색 input이 빈 값으로 남아 실패하는 것을 확인했다.
  • GREEN: AudioContentListPage의 URL read/write key만 search_word로 교체했다. npm run test:run -- src/features/audio-contents/tests/audio-list.test.tsx 결과 1 file / 2 tests passed였다.
  • REFACTOR/회귀: Character와 공통 query helper를 공유하지 않고 Audio page 내부 변경으로 끝냈다. npm run test:run -- src/features/audio-contents 첫 실행은 기존 audio-list.test.tsx 병렬 loading timeout 양상으로 실패했으나, 같은 명령 재실행 결과 8 files / 46 tests passed였다. 사용자 지시에 따라 mock E2E는 전체 Task 완료 전까지 보류했다. npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0 또는 no output이었다. 변경 TS 파일 LSP diagnostics와 Audio directory diagnostics는 오류 0건이었다.

Task R4.6 — Audio cover crop 준비 경계 보강

Goal 실행 P4-R6: Audio 생성·수정에서 cover crop source 준비와 연속 선택을 추적하고 crop 확정 전 저장을 차단한다.

  • 연결 리뷰: Phase 4 현재 리뷰REV-P4-009

  • 시작 조건: FILE-006~009, PRD §10.5 저장 중 feedback와 Audio cover 생성 필수/수정 optional 계약.

  • 완료 증거: 준비 중 edit 저장·stale source·준비 실패를 재현하는 실패 test, staged selection 최소 수정, Audio focused unit·mock E2E·file Gate 통과.

  • Files: Modify: src/features/audio-contents/components/AudioContentForm.tsx, 관련 Audio form/file tests와 tests/e2e/audio-content.spec.ts.

  • Interfaces: coverImage는 crop 적용 결과만 보유하고 준비/crop 중 submit을 비활성화·guard한다. 마지막 선택만 유효하며 취소는 기존 서버 cover를 유지하고 준비 실패를 inline 표시한다.

  • 범위 밖: 대용량 audio upload 상태 변경, 공통 crop 계산 변경, 범용 upload state library.

  • RED: edit에서 crop source 준비 중 저장, 느린 첫 선택의 늦은 dialog, 준비 reject 후 무반응을 실패 test로 고정한다.

  • GREEN: cover selection token·준비 상태·오류 처리와 submit guard를 추가한다.

  • REFACTOR: 기존 UploadState와 cover 준비 상태의 역할을 분리하고 필요한 표시 문구만 Audio form에 둔다.

  • 실행 명령: npm run test:run -- src/features/audio-contents; npm run e2e:mock -- tests/e2e/audio-content.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 준비/crop 미확정 cover의 저장 0건, stale dialog 0건, 정상 crop result와 기존 cover 유지 경계 일치.

  • 수동 확인: 기존 cover가 있는 수정 화면에서 느린 파일 두 개를 연속 선택하고 취소·적용·즉시 저장해 마지막 의도가 보존되는지 확인한다.

Progress — 2026-07-31 P4-R6 완료

  • RED: npm run test:run -- src/features/audio-contents/tests/audio-form-upload.test.tsx src/features/audio-contents/tests/audio-form-update.test.tsx에서 준비 중 저장 guard, stale selection, reject 오류 경계가 실패하는 것을 확인했다.
  • GREEN: AudioContentForm에 cover selection token, isCoverPreparing, crop dialog pending guard, reject inline 오류를 추가했다. coverImage는 crop 적용 결과만 commit하고, 취소는 수정 화면의 기존 서버 cover 유지 계약을 보존한다. focused 재실행 결과 2 files / 18 tests passed였다.
  • REFACTOR/회귀: FieldErrors 타입을 Partial<Record<...>>로 줄여 AudioContentForm.tsx 순수 LOC를 247로 낮췄다. npm run test:run -- src/features/audio-contents 결과 8 files / 53 tests passed였다. 사용자 지시에 따라 mock E2E는 전체 Task 완료 전까지 보류했다. npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0 또는 no output이었다. Audio directory LSP diagnostics는 16 TSX files / 오류 0건이었다.

Task R4.7 — Audio cover 원본 정책 사전 검증 복구

Goal 실행 P4-R7: Audio 생성·수정에서 새 cover 원본의 확장자·MIME pair와 exact byte 상한을 crop source 생성 전에 검증한다.

  • 연결 리뷰: Phase 4 현재 리뷰REV-P4-010

  • 시작 조건: FILE-001~003, FILE-006~007, PRD Image crop UI 흐름 1~3단계와 기존 validateAudioCoverFile 정책.

  • 완료 증거: 10,485,761 bytes, GIF, 확장자/MIME 불일치 cover가 createCropSource를 호출하거나 crop Dialog를 열지 않는 실패 test, 정상 JPEG/PNG crop 회귀 test, Phase 4 focused·mock E2E·정적 Gate 통과.

  • Files: Modify: src/features/audio-contents/components/AudioContentForm.tsx, 관련 Audio form tests와 필요 시 tests/e2e/audio-content.spec.ts.

  • Interfaces: 새 cover는 validateAudioCoverFile 성공 뒤에만 createCropSource로 전달한다. 실패 시 기존 coverErrorMessage 계열 inline 오류를 표시하고 create/edit 모두 기존 committed cover 상태를 보존한다.

  • 범위 밖: crop 계산·출력 크기 변경, audio 파일 upload 정책 변경, 새 공통 validation abstraction, server validation 변경.

  • Phase 4 Gate 연결: 기존 완료 기록은 보존하되 P4-R7 focused test와 mock Chromium Audio 시나리오, typecheck·lint·build 통과 전에는 이번 리뷰 회귀를 종결하지 않는다.

  • 검증 기록: RED/GREEN 명령·test 수·exit code와 수동 확인 결과를 이 Task 하단과 Phase 4 리뷰 §9 이후에 날짜순으로 누적한다.

  • RED: create/edit form에서 oversized PNG와 .png/image/jpeg 불일치 파일을 선택해 createCropSource 0회, crop Dialog 0개, 정책 오류 표시를 기대하는 실패 test를 작성한다.

  • GREEN: selectCoverImage의 crop 준비 전에 기존 Audio cover validator를 호출하고 실패 상태만 최소 처리한다.

  • REFACTOR: 정책 값과 메시지를 복제하지 않고 기존 validator·오류 변환을 재사용하며 Series/Character flow를 새 abstraction으로 묶지 않는다.

  • 실행 명령: npm run test:run -- src/features/audio-contents; npm run e2e:mock -- tests/e2e/audio-content.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build; git diff --check.

  • 기대 결과: 모든 명령 exit 0, invalid 원본의 crop source 호출·Dialog 노출·upload request 0건, valid JPEG/PNG의 기존 crop·create/edit 흐름 유지.

  • 수동 확인: 생성·수정 화면에서 oversized/mismatched/GIF/valid PNG를 차례로 선택해 inline 오류, 기존 cover 유지, 정상 crop 적용을 확인한다.

Progress — 2026-07-31 P4-R7 완료

  • RED: npm run test:run -- src/features/audio-contents/tests/audio-form-upload.test.tsx src/features/audio-contents/tests/audio-form-update.test.tsx는 2 files 중 2 tests failed / 18 passed였다. oversized PNG와 .png/image/jpeg replacement cover 모두 정책 오류가 없고 crop Dialog로 진입해 의도한 실패를 확인했다.
  • GREEN: AudioContentForm.selectCoverImagecoverErrorMessage를 crop source 준비 전에 재사용하게 수정했다. 같은 focused command는 2 files / 20 tests passed였고, invalid 원본에서 createCropSource 0회·crop Dialog 0개·inline 오류 표시를 확인했다.
  • REFACTOR/회귀: 새 공통 abstraction 없이 기존 validator·오류 변환만 재사용했다. AudioContentForm.tsx 순수 LOC는 249로 250 이하이며, npm run typecheck, npm run lint, npm run build는 exit 0이었다. src/features/audio-contents LSP diagnostics는 16 TSX files / 오류 0건이었다. 개발 중 E2E는 사용자 지시에 따라 보류했다.
  • 참고: npm run test:run -- src/features/audio-contents와 단독 npm run test:run -- src/features/audio-contents/tests/audio-list.test.tsxAudio list restores and serializes search_word only...오디오 콘텐츠 heading을 찾지 못하고 loading 상태에서 실패했다. 실패 경로는 Audio 목록 조회 test이며 이번 P4-R7의 cover 선택/form 변경 경로와 분리된다.

Task R4.8 — Audio upload 종료 처리의 단일 정산 보장

Goal 실행 P4-R8: Audio XHR upload의 성공·HTTP 실패·network 실패·취소가 경합해도 요청별 종료 처리를 한 번만 수행하고, 같은 보호 요청 burst의 401 만료 처리를 한 번만 유지한다.

  • 연결 리뷰: Phase 4 현재 리뷰REV-P4-011

  • 시작 조건: P1-R12 완료, PRD AUDIO-017, AUTH-005, §13의 동시 401 단일 처리, 기존 완료 P4-R2의 upload 401 burst contract.

  • 완료 증거: signal이 연결된 upload의 network error 뒤 abort가 발화한 다음 첫 session과 새 로그인 session의 protected upload가 각각 401을 반환하는 실패 test, 요청별 settle/cleanup 최소 수정, Audio focused·Phase 4 Gate와 검증 기록.

  • Files: Modify: src/features/audio-contents/api/upload-audio-content.ts, src/features/audio-contents/tests/audio-upload.test.ts; Create: src/features/audio-contents/tests/audio-upload-auth-lifecycle.test.ts.

  • Interfaces: uploadAudioContent는 각 XHR의 resolve/reject, 보호 upload count 감소와 AbortSignal listener 해제를 terminal event 종류와 무관하게 정확히 한 번 수행한다. 같은 protected upload burst의 clearSession/onAuthExpired는 최대 한 번 호출한다.

  • 범위 밖: fetch 기반 upload 전환, resumable upload, 새 상태 관리 library, 공통 API client 구조 변경, backend idempotency 변경.

  • Phase 4 Gate 연결: focused RED/GREEN 뒤 Audio 전체 unit, mock Chromium Audio 시나리오, typecheck·lint·build·diff 검증이 통과하기 전에는 완료 처리하지 않는다.

  • 검증 기록: RED의 실제 callback 횟수, GREEN/REFACTOR test 수와 Gate 결과를 이 Task 하단과 Phase 4 리뷰에 날짜순으로 누적한다.

  • RED: audio-upload.test.ts에 network error로 먼저 reject된 signal-bearing upload를 뒤늦게 abort한 다음 첫 session과 새 로그인 session의 401을 차례로 발생시켜 clearSession/onAuthExpired가 session별 1회, 총 2회여야 한다는 test를 추가하고 현재 총 1회만 호출되는 실패를 확인했다.

  • GREEN: 요청별 settled guard와 공통 terminal cleanup을 최소 적용해 count 감소·listener 해제·Promise settle을 한 번만 수행했다.

  • REFACTOR: onload, onerror, abort가 같은 정산 경계를 사용하도록 중복만 정리하고 기존 success/progress/415/malformed response/cancel retry contract를 유지했다.

  • 실행 명령: npm run test:run -- src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-upload-auth-lifecycle.test.ts; npm run test:run -- src/features/audio-contents; npm run e2e:mock -- tests/e2e/audio-content.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; git diff --check.

  • 기대 결과: 모든 명령 exit 0, 신규 lifecycle test에서 두 session의 401 callback 각각 1회, 기존 upload progress·cancel·retry·오류 mapping 회귀 0건.

  • 수동 확인: 느린 network에서 upload 오류와 취소를 인접하게 발생시킨 뒤 재시도하고, 이후 401 응답에서 로그인 이동·만료 안내가 중복되지 않는지 확인한다.

P4-R8 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/features/audio-contents/tests/audio-upload.test.ts — 1 failed / 11 passed. 신규 lifecycle test에서 network error 후 late abort가 다음 session 401을 삼켜 clearSession 기대 2회 대비 실제 1회였다.
  • GREEN: uploadAudioContent에 요청별 isSettled guard와 공통 terminal cleanup을 추가해 resolve/reject/count 감소/AbortSignal listener 제거가 terminal 종류와 무관하게 한 번만 실행되도록 했다. 같은 focused 명령은 1 file / 12 tests passed였다.
  • REFACTOR/회귀: 기존 success/progress/415/malformed response/cancel retry contract를 유지했다. reviewer blocker로 fetch-first 순서 회귀를 추가한 뒤 auth 주입 시 storage fallback을 쓰지 않도록 보완했다. npm run test:run -- src/shared/api/__tests__/client-auth.test.ts src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-upload-auth-lifecycle.test.ts — 3 files / 22 tests passed. npm run test:run -- src/features/audio-contents — 9 files / 58 tests passed. npm run test:run -- src/app src/features/auth src/shared/api src/features/audio-contents/tests/audio-upload.test.ts — 13 files / 91 tests passed. npm run test:run — 79 files / 397 tests passed. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, targeted git diff --check — 모두 exit 0. build는 기존 503.04kB chunk warning만 표시했다. LSP diagnostics는 변경 파일·디렉터리 기준 오류 0건이었다.
  • E2E/수동 확인: 사용자 지시에 따라 개발 중 E2E는 반복 실행하지 않고 모든 Task 구현 뒤 필요 시 수행한다.

Task R4.9 — 공통 오디오 플레이어 compact UI

Goal 실행 P4-R9: 공통 AdminAudioPlayer를 image·video 영역 없는 compact audio-only UI로 개선해 오디오 콘텐츠 목록·상세와 커뮤니티 목록·Sheet에 동일하게 적용한다.

  • Reference packet: .omo/evidence/p4-r9-reference/plyr-audio-{375,768,1280}.png, .omo/evidence/p4-r9-reference/media-chrome-audio-{375,768,1280}.png. Plyr의 밝은 52px compact bar를 primary surface로, Media Chrome의 시간·배속·음량 control anatomy를 secondary structure로 사용한다.

  • 시작 조건: PRD AUDIO-021, AUDIO-026, AUDIO-034와 기존 AdminAudioPlayer·AudioPlaybackProvider 단일 재생 계약.

  • 완료 증거: 아래 RED/GREEN/REFACTOR 체크박스, focused·전체 unit·mock E2E·typecheck·lint·build 통과, Chromium/mobile Chrome 실제 화면 screenshot과 visual QA 판정, QA resource teardown 기록.

  • 범위 밖: 새 audio player dependency, waveform, playlist, download control, cover/poster/video viewport, API·DTO·signed URL lifecycle 변경.

Files:

  • Modify: src/shared/ui/admin-audio-player.tsx
  • Modify: src/shared/ui/__tests__/admin-audio-player.test.tsx
  • Verify unchanged: src/features/audio-contents/components/AudioContentList.tsx
  • Verify unchanged: src/features/audio-contents/components/AudioContentListItem.tsx
  • Verify unchanged: src/features/audio-contents/pages/AudioContentDetailPage.tsx
  • Verify unchanged: src/features/community-posts/components/CommunityPostList.tsx
  • Verify unchanged: src/features/community-posts/components/CommunityPostListItem.tsx
  • Verify unchanged: src/features/community-posts/components/CommunityPostSheet.tsx
  • Test: src/features/audio-contents/tests/audio-list.test.tsx
  • Test: src/features/audio-contents/tests/audio-player.test.tsx
  • Test: src/features/community-posts/tests/community-list.test.tsx
  • Test: src/features/community-posts/tests/community-sheet.test.tsx
  • E2E: tests/e2e/audio-content.spec.ts
  • E2E: tests/e2e/community.spec.ts

Interfaces:

  • Consumes: AdminAudioPlayerProps { playerId, src, title }, useAudioPlayback, native HTMLAudioElement events.
  • Produces: 기존 props와 재생 동작을 바꾸지 않는 compact audio-only control; 재생 진행재생 설정 accessible group.

시나리오 계약:

시나리오 이진 통과 조건 실제 surface 자동 test
Happy path 오디오 목록에서 재생 버튼을 누르면 버튼 이름이 일시정지로 바뀌고, 상시 설명 label 없는 재생·진행 slider·현재/전체 시간·배속·음량 control이 표준 viewport에서 Plyr/Media Chrome처럼 한 줄에 표시된다. mock Chromium /ai-characters/101/audio-contents에서 page.click({ name: "재생" }) 후 reference-fidelity screenshot src/shared/ui/__tests__/admin-audio-player.test.tsxAdminAudioPlayer matches the compact reference control anatomy
Edge 320px viewport와 200% zoom에서 player의 scrollWidth <= clientWidth이고 모든 control이 viewport 안에 있다. mock mobile Chrome 목록·상세 screenshot과 overflow probe tests/e2e/audio-content.spec.ts의 기존 320px·200% zoom player 시나리오
Adjacent regression 커뮤니티 목록과 Sheet가 같은 player 구조를 사용하고 두 player 중 새 항목 재생 시 기존 항목이 pause되며 오류·수동 재시도 계약이 유지된다. mock Chromium /ai-characters/101/community-posts 목록·Sheet 재생과 screenshot src/shared/ui/__tests__/admin-audio-player.test.tsx, src/features/community-posts/tests/community-sheet.test.tsx, tests/e2e/community.spec.ts

TDD 절차:

  • RED: 실패 테스트 작성/실패 확인src/shared/ui/__tests__/admin-audio-player.test.tsx에 다음 reference-anatomy test를 작성한다.

    production code를 수정하기 전에 mock Chromium의 오디오 콘텐츠 목록·상세와 커뮤니티 목록·Sheet 현재 화면을 reference screenshot으로 저장한다.

    test("AdminAudioPlayer matches the compact reference control anatomy", () => {
      render(<AdminAudioPlayer playerId="one" src="https://cdn.example.com/audio.m4a" title="샘플 오디오" />);
    
      const player = screen.getByRole("group", { name: "샘플 오디오 오디오 플레이어" });
      const controls = within(player).getByRole("group", { name: "재생 제어" });
      expect(within(controls).getByRole("button", { name: "재생" })).toBeInTheDocument();
      expect(within(controls).getByRole("slider", { name: "재생 위치" })).toBeInTheDocument();
      expect(within(controls).getByRole("combobox", { name: "재생 속도" })).toBeInTheDocument();
      expect(within(controls).getByRole("slider", { name: "볼륨" })).toBeInTheDocument();
      expect(within(player).queryByText("볼륨")).not.toBeInTheDocument();
      expect(within(player).queryByText("재생 속도")).not.toBeInTheDocument();
      expect(player.querySelector("img")).not.toBeInTheDocument();
      expect(player.querySelector("video")).not.toBeInTheDocument();
    });
    

    npm run test:run -- src/shared/ui/__tests__/admin-audio-player.test.tsx -t "compact reference control anatomy" 실행 시 재생 제어 group을 찾지 못해 실패해야 한다.

  • GREEN: 최소 구현/통과 확인src/shared/ui/admin-audio-player.tsx의 기존 state·handler·native <audio>는 유지하고 반환 JSX만 다음 구조로 교체한다.

    <section aria-label={`${title} 오디오 플레이어`} className="flex min-w-0 flex-col gap-2 rounded-lg border border-border bg-card px-1 py-1 sm:px-3" onKeyDown={handleKeyDown} role="group" tabIndex={0}>
      <audio controlsList="nodownload" onDurationChange={(event) => setDuration(event.currentTarget.duration)} onEnded={() => { setIsPlaying(false); clearPlayer(); }} onError={() => { setHasError(true); setIsPlaying(false); }} onPause={() => setIsPlaying(false)} onPlay={() => setIsPlaying(true)} onTimeUpdate={(event) => setCurrentTime(event.currentTarget.currentTime)} preload="metadata" ref={audioRef} src={src} />
      <div aria-label="재생 제어" className="flex min-w-0 flex-wrap items-center gap-1 sm:gap-2" role="group">
        <button aria-label={isPlaying ? "일시정지" : "재생"} className="grid size-11 shrink-0 place-items-center rounded-full border border-input bg-primary text-primary-foreground hover:bg-[var(--button-bg-hover)] active:bg-[var(--button-bg-active)]" onClick={togglePlay} type="button">
          <svg aria-hidden="true" className="size-5" fill="currentColor" viewBox="0 0 24 24">
            {isPlaying ? <path d="M7 5h4v14H7zm6 0h4v14h-4z" /> : <path d="m8 5 11 7-11 7z" />}
          </svg>
        </button>
        <input aria-label="재생 위치" className="h-11 min-w-11 flex-1 accent-primary" max={duration || 0} min="0" onChange={(event) => changeCurrentTime(Number(event.currentTarget.value))} step="1" type="range" value={currentTime} />
        <span className="shrink-0 text-xs tabular-nums text-muted-foreground">{formatTime(currentTime)}/{formatTime(duration)}</span>
        <select aria-label="재생 속도" className="min-h-11 w-11 shrink-0 rounded-md border border-input bg-card px-1 text-center text-base font-semibold sm:text-sm" defaultValue="1" onChange={(event) => changePlaybackRate(Number(event.currentTarget.value))}>
          <option value="0.75">0.75×</option><option value="1">1×</option><option value="1.25">1.25×</option><option value="1.5">1.5×</option><option value="2">2×</option>
        </select>
        <svg aria-hidden="true" className="size-4 shrink-0 text-muted-foreground" fill="currentColor" viewBox="0 0 24 24"><path d="M4 9v6h4l5 4V5L8 9zm11.5 3a3.5 3.5 0 0 0-1.5-2.87v5.74A3.5 3.5 0 0 0 15.5 12Zm-1.5-7.46v2.06a6 6 0 0 1 0 10.8v2.06a8 8 0 0 0 0-14.92Z" /></svg>
        <input aria-label="볼륨" className="h-11 min-w-11 basis-11 shrink grow-0 accent-primary" defaultValue="1" max="1" min="0" onChange={(event) => changeVolume(Number(event.currentTarget.value))} step="0.05" type="range" />
      </div>
      {hasError ? <div className="flex min-w-0 flex-col gap-2 rounded-md border border-destructive bg-card p-3 text-sm text-destructive" role="alert"><p className="break-words">오디오를 재생할  없습니다. 페이지 새로고침  다시 시도하세요.</p><button className="rounded-md border border-input bg-card px-3 py-2 font-semibold hover:bg-accent" onClick={retry} type="button">오디오 다시 시도</button></div> : null}
    </section>
    

    같은 focused 명령이 exit 0이고 신규 test가 통과해야 한다.

  • REFACTOR: 정리/회귀 확인 — event handler나 player context를 추출하지 않고 JSX의 반복 class와 접근성 이름만 점검한다. src/shared/ui/admin-audio-player.tsx를 250 pure LOC 이하로 유지하고 기존 signed URL 비기록, 단일 재생, keyboard, seek, volume, speed, 오류·수동 재시도 test를 모두 통과시킨다.

검증 기준:

  • 실행 명령:

    npm run test:run -- src/shared/ui/__tests__/admin-audio-player.test.tsx src/features/audio-contents/tests/audio-list.test.tsx src/features/audio-contents/tests/audio-player.test.tsx src/features/community-posts/tests/community-list.test.tsx src/features/community-posts/tests/community-sheet.test.tsx
    npm run test:run
    npm run e2e:mock -- tests/e2e/audio-content.spec.ts tests/e2e/community.spec.ts
    npm run typecheck
    npm run lint
    npm run build:dev
    npm run build:prod
    git diff --check -- src/shared/ui/admin-audio-player.tsx src/shared/ui/__tests__/admin-audio-player.test.tsx docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md
    
  • 기대 결과: 모든 명령 exit 0; 신규 compact group test와 기존 전체 unit·Chromium/mobile Chrome mock E2E 통과; 변경 TSX LSP diagnostics 오류 0건; 다운로드 control·image·video·waveform 0개.

  • 수동 확인: npm run dev:mock을 실행하고 Playwright로 오디오 콘텐츠 목록·상세, 커뮤니티 목록·Sheet를 desktop 1280px와 mobile 320px에서 연다. 재생, seek, 볼륨, 배속, 단일 재생, 오류·재시도를 조작하고 각 surface screenshot을 저장한다. 200% zoom에서 clipping·overflow를 검사하고 visual-qa의 pixel diff 및 디자인 시스템/기능 무결성·시각 충실도/CJK 판정을 모두 통과시킨다.

  • QA teardown: Playwright browser/context를 닫고 mock dev server PID를 종료한 뒤 8889 port listener가 없음을 확인해 Progress에 기록한다.

  • RED·GREEN·REFACTOR, browser artifact 경로, visual QA 판정, teardown 결과를 이 Task 하단에 누적한다.

전체 중단 조건: P4-R9의 세 시나리오가 RED→GREEN 및 실제 Chromium/mobile Chrome surface에서 통과하고, 전체 unit·mock E2E·typecheck·lint·build가 green이며 LSP 오류와 QA resource가 0개일 때 즉시 종료한다.

P4-R9 구현·검증 기록 — 2026-08-03:

  • RED: 첫 reference-anatomy test는 재생 제어 group 부재로 1 failed / 5 skipped였다. 후속 폭·접근성 RED는 volume의 hidden class, seek/rate/volume 최소 폭·font·basis, 표준 viewport 단일 행 폭 예산 assertion이 각각 기존 구현에서 실패했다.
  • GREEN: native <audio>와 기존 상태·handler는 유지하고 Plyr의 밝은 compact surface와 Media Chrome의 명시적 time/rate/volume anatomy를 semantic token 기반 live DOM으로 구현했다. 최종 focused player test는 1 passed / 5 skipped, player 전체는 6 passed였다.
  • REFACTOR: 새 dependency·consumer 변경·상태 abstraction 없이 class와 accessible name만 정리했다. AdminAudioPlayer는 250 pure LOC 이하이며 변경 TSX 2개 LSP diagnostics는 오류 0건이다.
  • 자동 검증: focused 5 files / 19 tests, 전체 81 files / 426 tests, npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, git diff --check가 모두 exit 0이었다. 두 build에는 기존 500kB chunk warning만 남았다.
  • 브라우저 V3: npm run e2e:mock -- tests/e2e/audio-content.spec.ts tests/e2e/community.spec.ts는 Chromium/mobile Chrome에서 25 passed / 1 intentional skip / 0 failed였다. .omo/evidence/p4-r9-v3/의 player 16개·context 16개·상태 3개 fresh PNG에서 375/768/1280의 12개 표준 조합은 모두 5455px 단일 행, 320px/200% 4개 조합은 모든 control을 유지한 채 overflow 없이 wrap했다.
  • 기능·시각 판정: play/pause, seek, rate 1.5, volume 0.5, 단일 active track, 일반 오류·수동 retry와 API refetch delta 0을 확인했다. 독립 design-system/functional 및 visual/CJK reviewer가 같은 V3 세트를 각각 PASS로 판정했고 blocker는 없다.
  • Teardown: Playwright browser는 종료됐다. QA-owned server는 없었고, QA 이전부터 실행 중인 8889의 PID 4837/4875는 사용자 프로세스로 보존했다.

Task R4.10 — Audio form 입력 정책과 조건부 설정 UX 정렬

Goal 실행 P4-R10: 2026-08-04 확정 Audio form 정책을 구현하고 create/update payload 기본값과 접근 가능한 native control UX를 회귀로 고정한다.

  • 시작 조건: P4-R9 완료, PRD AUDIO-008~010, AUDIO-019, AUDIO-033과 2026-08-04 Decision Log 확인.
  • 완료 증거: tags, 숫자 전용 가격, 가격 조건부 설정·초기화, 숨긴 OpenAPI field의 고정 payload, 예약 datetime과 preview duration offset control, 선택 카드 문법이 RED/GREEN/REFACTOR test와 Chromium/mobile Chrome mock browser QA로 검증된다. 실행 결과와 QA teardown을 §7 검증 기록에 누적한다.
  • 범위 밖: OpenAPI/backend schema 변경, limited 의미 추정, isOnlyRental 동기화 규칙 발명, custom date/time picker, 새 form library, 기존 dirty AudioContentForm.tsxaudio-form.test.tsx의 사용자 reorder 변경.

Files:

  • Modify: DESIGN.md
  • Modify: src/features/audio-contents/components/AudioContentForm.tsx
  • Create: src/features/audio-contents/components/AudioTagInput.tsx
  • Create: src/features/audio-contents/components/AudioContentCreateOptions.tsx
  • Modify: src/features/audio-contents/components/ReleaseScheduleField.tsx
  • Modify: src/features/audio-contents/components/audio-content-form-helpers.ts
  • Modify: src/features/audio-contents/tests/audio-form.test.tsx
  • Modify: src/features/audio-contents/tests/audio-form-update.test.tsx
  • Modify: src/features/audio-contents/tests/audio-form-create-red.test.tsx by consolidating its create assertions into the focused form suite, then remove the redundant file only after those assertions pass there
  • Modify: tests/e2e/audio-content.spec.ts

Interfaces:

  • AudioTagInput({ value, onChange }) consumes and emits a comma-separated string; it renders chips, commits nonempty trimmed entries on Enter or comma, and exposes a labeled remove button for each chip.
  • AudioContentCreateOptions({ price, value, onChange }) owns only create-only option controls. For price > 0, it renders native selection-card controls for purchaseOption, isGeneratePreview, isPointAvailable, isAdult, isCommentAvailable, and isFullDetailVisible. For price 0, it hides purchase/preview/point controls and emits purchaseOption: "BOTH", isGeneratePreview: false, isPointAvailable: false, previewStartTime: null, previewEndTime: null.
  • Price form state accepts digits only and serializes an integer in 0..99999. 0 means free. Create serialization always sends limited: null, languageCode: null, and isOnlyRental: false, without rendering controls for those fields.
  • ReleaseScheduleField({ releaseMode, value, onChange }) renders native radio selection cards and only renders <input type="datetime-local"> in scheduled mode. It does not call showPicker().
  • Preview start/end controls render only when isGeneratePreview is true. They use text inputs for audio duration offsets in full HH:MM:SS; create serialization sends the entered HH:mm:ss value without shorthand normalization.
  • DESIGN.md defines one selection-card visual grammar for native radio and checkbox controls, preserving visible labels, keyboard operation, focus indication, and 44px minimum targets.

TDD 예외 사유: 이 Task의 PRD·plan 갱신은 문서 산출물만 바꾸므로 application behavior를 대상으로 한 새 실패 test를 만들지 않는다. 문서 계약 test와 Markdown whitespace 검사가 해당 변경의 직접 검증이다. 아래 RED/GREEN/REFACTOR는 애플리케이션 구현 시작 후에만 실행한다.

대체 검증 방법: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts와 scoped git diff --check로 문서 계약과 형식을 확인하고, 실제 application RED/GREEN/REFACTOR 결과는 실행 뒤 Progress에만 기록한다.

TDD 절차:

  • RED: 실패 테스트 작성/실패 확인audio-form.test.tsx에 Enter/comma chip 추가, remove button, comma-separated payload, numeric price와 0/99999 허용·100000 차단을 추가한다. audio-form-update.test.tsx에 update payload가 create-only control을 다시 보내지 않는 경계를 추가한다. audio-form-create-red.test.tsx의 create assertion은 focused 회귀로 유지한다. tests/e2e/audio-content.spec.ts에 예약/즉시 전환과 가격 0 전환 시 옵션 숨김·기본값 초기화, ArrowDown/ArrowUp min 0 stepper를 추가한다. focused Vitest가 초기 가격 ""/text input과 기존 payload assertion으로 실패하는지 확인했다.
  • GREEN: 최소 구현/통과 확인AudioTagInput, AudioContentCreateOptions, helper와 existing form/schedule field에 필요한 최소 조합만 추가했다. 가격 기본값 0, native type="number" min="0" step="1", price=0 reset, fixed limited/languageCode/isOnlyRental serialization, 예약 datetime과 preview duration offset input 조건부 렌더링, radio/checkbox selection card를 구현하고 focused command를 통과시켰다.
  • REFACTOR: 정리/회귀 확인 — create-only option state를 AudioContentCreateOptions에 국한하고 form helper의 serialization 중복만 정리했다. DESIGN.md와 구현의 선택 카드 규칙을 대조한 뒤 focused form tests, Audio feature regression, typecheck, lint, build, mock E2E를 실행했다.

검증 기준:

  • 실행 명령: npm run test:run -- src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-update.test.tsx src/features/audio-contents/tests/audio-form-create-red.test.tsx; npm run test:run -- src/features/audio-contents; npm run typecheck; npm run lint; npm run build; npm run e2e:mock -- tests/e2e/audio-content.spec.ts; npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts; git diff --check -- DESIGN.md docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md src/features/audio-contents tests/e2e/audio-content.spec.ts.
  • 기대 결과: 실행한 모든 명령이 exit 0이다. tags payload는 comma-separated string이고, price는 native number input으로 0..99999 integer를 보낸다. price=0에서 purchase/preview/point control과 preview time은 없고 지정 기본값만 전송된다. limited=null, languageCode=null, isOnlyRental=false가 유지되며 세 field의 UI와 임의 동기화는 없다. 예약은 native datetime-local, preview는 full HH:MM:SS text duration offset input으로 조건부 렌더링한다. preview request는 입력한 HH:mm:ss 값을 그대로 보내며 showPicker() 호출은 없다.
  • 수동 확인: npm run dev:mock으로 desktop Chromium과 mobile Chrome에서 Audio 생성 form을 연다. keyboard로 tags를 Enter와 comma로 추가하고 remove button으로 삭제한다. 가격을 유료값에서 0으로 바꿔 숨김과 reset을 확인하고 다시 유료값으로 바꾼다. 예약 공개와 preview 생성 on/off를 전환해 native datetime과 text duration offset input, selection-card focus, 200% zoom, axe critical·serious 0건을 확인한다. mock browser와 dev server를 종료하고 QA가 시작한 listener만 정리한다.
  • RED/GREEN/REFACTOR의 실제 명령·test 수·browser QA·teardown 결과를 새 Progress 기록에 누적한다.

P4-R10 실행 전 Progress — 2026-08-04:

  • 문서 정책 결정과 실행 계약만 확정했다. 애플리케이션 source, DESIGN.md, form test, E2E는 아직 수정하거나 실행하지 않았으므로 RED/GREEN/REFACTOR 결과와 browser QA 통과를 기록하지 않는다.
  • 문서 대체 검증은 이 문서 갱신 직후 mock-preview-docs.test.ts와 scoped git diff --check로 수행하며, 그 결과는 현재 문서 변경의 검증일 뿐 P4-R10 구현 완료 증거가 아니다.

P4-R10 완료 Progress — 2026-08-04:

  • RED/GREEN: preview duration 회귀는 type="text" 기대에 기존 type="time"이 반환되어 RED를 확인했고, 최소 구현 후 focused 1/1, create form 10/10, Audio 66/66, 전체 Vitest 81 files / 434 tests, 문서 계약 9/9가 통과했다.
  • 가격 스테퍼 보완: 가격 기본값 0과 native type="number" min="0" step="1" 회귀는 focused test에서 기존 빈 문자열/text input으로 RED를 확인했다. 최소 구현 후 focused form 18/18, Audio 65/65, 전체 Vitest 81 files / 433 tests, typecheck, lint, build:prod, mock E2E 11 passed / 1 intentional skip가 통과했다. Chromium/mobile Chrome에서 ArrowDown at 0은 0 유지, ArrowUp은 1 증가를 확인했다.
  • 정적 검증: npm run typecheck, npm run lint, npm run build:prod, TypeScript no-excuse 검사, git diff --check가 exit 0이었다. production build에는 기존 500kB chunk warning만 남았다.
  • 브라우저 검증: npm run e2e:mock -- tests/e2e/audio-content.spec.ts는 Chromium/mobile Chrome에서 11 passed / 1 intentional skip / 0 failed였다. 실제 1280/768 DOM에서 preview start/end는 type=text, step=null, 값 00:00:30/01:00:05, input[type=time]=0이었고 예약 입력만 datetime-local이었다. 375px은 기존 정책대로 mutation form을 숨겼으며 세 viewport 모두 horizontal overflow가 없었다.
  • 시각·리뷰 판정: .playwright-mcp/audio-preview-duration-{1280,768,375-blocked}-20260804.png fresh 캡처를 대상으로 design-system/functional 및 visual/CJK Oracle이 모두 blocker 없이 PASS했고, 최종 code reviewer도 APPROVE했다.
  • Teardown: Playwright browser를 닫고 QA가 시작한 Vite PID 69030과 parent PID 69001을 종료했다. 이후 127.0.0.1:8889 listener가 없음을 확인했다.

Phase 5. Series vertical slice

목표: 선택 Character의 Series를 조회하고, 계약 제공 후 생성·수정·비활성화하며 Audio 연결·해제와 서버가 반환한 Series 전체 순서를 관리한다.

Phase Goal P5: Task 5.1 → 5.5와 Phase 5 Gate로 Series 조회, 계약 제공 후 CRUD, 연결·전체 순서 slice를 완성한다.

  • 시작 조건: P4-T2의 Audio 조회 API 완료.
  • 완료 조건: P5-T1~P5-T5, P5-GATE 완료. genre lookup 외부 의존은 제공 또는 명시적 후속/제외 상태로 종결하고, Series 수정은 상세 응답의 수정용 원본값으로 초기화한다.
  • 실행 순서: 계약 확인 → 목록/상세 → CRUD → 연결/순서 → 반응형·접근성.

요구사항: SERIES-001~018, FILE-001~002, FILE-005, FILE-007~009, FILE-012, FILE-015, PRD 9의 Series 범위.

외부 의존: EXT-002 genre lookup(SERIES-007, SERIES-011)은 legacy 후보 GET /creator-admin/audio-content/series/genre로 구현 계획에 포함한다. Series 수정 form은 별도 edit DTO 없이 상세 응답이 list item과 같은 genreId, enum publishedDaysOfWeek, enum state를 제공하면 초기화할 수 있다. EXT-007 active-only 반환 보장은 외부 의존이다. OpenAPI 밖의 도메인별 오류 message key는 분기하지 않는다. 연결 후보·page 기반 전체 로딩 endpoint는 OpenAPI에 제공됐다.

주요 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

Phase 5 Task 실행 계약

Goal Files Interfaces TDD·검증 기준
P5-T1 Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md; Read: docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json; Test: 없음 Consumes: Series* schema·9 operation. Produces: CRUD·contents/search·order contract map, 외부 의존과 screen inventory TDD 예외: 외부 계약 조사 Task다. OpenAPI JSON parse와 rg -n 'SERIES-01[4-8]' 문서 추적 검사를 실행한다. 기대 exit 0. 수동 확인: genre lookup 차단, 상세 응답의 수정용 원본값, 제공된 link/order 범위가 분리된다.
P5-T2 Create: src/features/series/api/series-api.ts, src/features/series/model/types.ts, src/features/series/pages/{SeriesListPage,SeriesDetailPage}.tsx, src/features/series/components/{SeriesList,SeriesListItem,SeriesSummary}.tsx, src/features/series/tests/series-contract.test.ts Consumes: SeriesListResponse, SeriesDetailResponse. Produces: getSeries({characterId,page,size}), getSeriesDetail({characterId,seriesId})와 read-only 조회 UI TDD 적용: npm run test:run -- src/features/series/tests/series-contract.test.ts; 기대 exit 0. 수동 확인: 목록 enum과 상세 표시 문자열, 직접 링크 조회.
P5-T3 Create: src/features/series/schemas/series-schema.ts, src/features/series/validation/series-image-policy.ts, src/features/series/pages/SeriesFormPage.tsx, src/features/series/components/{SeriesForm,PublishedDaysField,GenreCombobox}.tsx, src/features/series/tests/series-form.test.tsx; Modify: src/features/series/api/series-api.ts, src/features/series/tests/series-contract.test.ts Consumes: create/update multipart, legacy genre lookup GET /creator-admin/audio-content/series/genre, 상세 응답의 genreId·enum 요일·enum state. Produces: createSeries, updateSeries, deactivateSeries, listSeriesGenres TDD 적용: npm run test:run -- src/features/series/tests/series-contract.test.ts src/features/series/tests/series-form.test.tsx; 기대 exit 0. 수동 확인: genre 선택, keyword/image/state와 직접 edit 초기화.
P5-T4 Create: src/features/series/pages/SeriesOrderPage.tsx, src/features/series/components/{SeriesContents,SeriesOrderList}.tsx, src/features/series/tests/{series-contents,series-order}.test.tsx; Modify: src/features/series/api/series-api.ts, src/features/series/pages/SeriesDetailPage.tsx Consumes: SeriesContentListResponse, SeriesContentSearchItem, SeriesContentAddRequest, SeriesOrderUpdateRequest. Produces: searchUnlinkedContents, addSeriesContents, removeSeriesContent, updateSeriesOrder TDD 적용: npm run test:run -- src/features/series/tests/series-contents.test.tsx src/features/series/tests/series-order.test.tsx; 기대 exit 0. 수동 확인: contentIdList·ids request와 keyboard reorder.
P5-T5 Modify: src/features/series/pages/{SeriesListPage,SeriesDetailPage,SeriesFormPage,SeriesOrderPage}.tsx, src/features/series/components/{SeriesList,SeriesListItem,SeriesSummary,SeriesForm,PublishedDaysField,GenreCombobox,SeriesContents,SeriesOrderList}.tsx; Test: tests/e2e/series.spec.ts Consumes: P5-T2~T4 활성 UI. Produces: viewport·keyboard capability evidence TDD 적용: npm run e2e:mock -- tests/e2e/series.spec.ts; 기대 활성 계약 범위 통과. 수동 확인: 320px 조회, 200% zoom, keyboard, axe.

P5-T2~P5-T5는 각 row의 focused test로 RED → GREEN → REFACTOR를 실행한다. 외부 계약 때문에 RED test의 기대 동작 자체를 정할 수 없으면 test를 skip하지 않고 해당 network 범위를 시작하지 않으며, 대체 검증과 남은 조건을 §7 검증 기록에 남긴다.

Task 5.1 Phase 계약 확인

Goal 실행 P5-T1: Series genre·연결 후보·전체 순서·오류 계약, mock scenario와 component map을 확정한다.

  • 시작 조건: P4-T2 완료, PRD SERIES-001~018, MOCK-001~009와 OpenAPI Series 9개 operation/schema 확인.

  • 완료 증거: 체크박스 전체, 제공 계약 또는 제외 결정의 세 문서 일치, 상태/action inventory.

  • 범위 밖: active-only·도메인별 오류 동작의 추정 구현. genre lookup은 legacy API를 사용하고, 별도 edit DTO는 만들지 않는다.

  • genre lookup endpoint·DTO·search/page 계약을 기록한다.

  • 연결 후보 GET .../contents/search?search_word=..., 연결 {contentIdList}, 해제 body 없는 DELETE, 전체 순서 {ids} 계약을 기록한다.

  • 목록 data.totalCount/itemspage/size로 전체 Series page를 누락 없이 읽는 방식을 기록한다. 도메인별 누락 ID·동시 변경 오류 key는 제공되지 않았음을 외부 의존으로 남긴다.

  • legacy genre lookup API와 active-only 보장의 영향 범위를 문서에서 먼저 확인한다. active-only는 추측 구현하지 않는다.

  • 목록·상세·form·연결·순서 화면의 상태/action inventory를 작성하고 Page는 route/query/policy 조합, feature component는 Series 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.

  • 제공 계약 범위만 Series 목록·상세·연결·순서 browser fixture로 만들고 genre lookup·active-only·충돌 계약 미제공 부분은 mock에서도 추정하지 않는다. Series 수정 초기화는 상세 응답의 수정용 원본값으로 처리한다.

P5-T1 계약 확인 결과:

  • OpenAPI Series operation은 9개다. listAiCharacterSeries, createAiCharacterSeries, reorderAiCharacterSeries, getAiCharacterSeries, updateAiCharacterSeries, listAiCharacterSeriesContents, addAiCharacterSeriesContents, searchAiCharacterContentsNotInSeries, removeAiCharacterSeriesContent를 기준으로 한다.
  • Series 생성·수정 contract는 제공됐고, 장르 lookup은 OpenAPI에는 없지만 legacy 후보 GET /creator-admin/audio-content/series/genre를 사용한다. P5-T3에서 response DTO를 contract test로 고정하고 장르 선택 network integration과 생성 완료 판정을 진행한다.
  • 별도 EXT-003 edit DTO 또는 표시 문자열 mapping 계약은 필요 없다. 상세 응답이 list item과 같은 genreId, enum publishedDaysOfWeek, enum state를 함께 제공하면 직접 링크 수정 form을 초기화할 수 있다. 상세의 표시용 문자열은 update enum/ID로 역변환하지 않는다.
  • 연결 후보는 GET /api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents/search?search_word=...이고 body가 없다. 응답은 data[]contentId, title, coverImage만 사용한다.
  • 연결은 POST /api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents에 JSON { "contentIdList": [...] }를 보낸다. 해제는 DELETE /api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents/{contentId}이고 request body가 없다.
  • 순서 변경은 PUT /api/v2/admin/ai-characters/{characterId}/series/orders에 JSON { "ids": [...] }를 보낸다. {contentIds}, {seriesIds}는 사용하지 않는다.
  • Series 목록과 연결 콘텐츠 목록은 모두 page, size query와 data.totalCount, data.items[]를 사용한다. 순서 변경 화면은 첫 page의 totalCount를 기준으로 ceil(totalCount / size)까지 같은 size로 순차 또는 병렬 조회해 모든 page를 모은 뒤, 현재 수집한 items.lengthtotalCount와 일치할 때만 전체 ID 순서를 저장한다. 일부 page 실패나 개수 불일치는 저장을 막고 재조회 안내로 처리한다.
  • EXT-007 active-only 반환 보장, 순서 저장의 missing ID와 concurrent conflict status/message key는 OpenAPI에 없다. mock과 UI는 공통 ApiErrorResponsesuccess=false, message, data=null, errorProperty와 공통 status만 사용하고 도메인 분기를 추정하지 않는다.
  • 화면 상태와 action inventory: 목록은 loading, empty, error, background refresh, read-only, page 변경, 상세 이동을 가진다. 상세는 loading, error, read-only, 연결 콘텐츠 조회, desktop/tablet에서 생성·수정·비활성화·연결·해제·순서 이동 entry를 가진다. form은 계약 대기 상태, field validation, 저장 중, 성공, 오류, 이탈 확인, desktop/tablet 전용 action을 가진다. 연결 화면은 후보 검색, 후보 없음, 이미 연결됨, 연결 중, 해제 확인, 오류와 재시도를 가진다. 순서 화면은 전체 page 로딩, 부분 실패, drag reorder, keyboard 위/아래 이동, 저장 중, 충돌 또는 누락 ID 오류, 재조회 action을 가진다.
  • Component map: SeriesListPageSeriesDetailPage는 route param, query, permission, data loading만 조합한다. SeriesList, SeriesListItem, SeriesSummary는 목록 enum과 상세 표시 문자열을 그대로 표시한다. SeriesForm, PublishedDaysField, GenreCombobox는 legacy genre lookup과 상세 수정용 원본값으로 network payload를 활성화한다. SeriesContents는 search/add/remove 규칙을, SeriesOrderList는 full-page read와 reorder 규칙을 소유한다. 공통 상태, pagination, confirm dialog, file field, image crop, unsaved guard는 Phase 1 shared contract를 사용한다.
  • Mock scenario는 제공된 목록, 상세, legacy genre lookup, 연결 콘텐츠 목록, 연결 후보 검색, 연결, 해제, 전체 순서 happy path와 공통 오류만 만든다. active-only server 결과, missing ID와 concurrent conflict 도메인 오류는 fixture에 넣지 않는다.

Task 5.2 Series 목록·상세 조회

Goal 실행 P5-T2: OpenAPI가 제공한 Series 목록·상세 조회와 서로 다른 응답 DTO 표시 규칙을 완성한다.

  • 시작 조건: P5-T1 완료.

  • 완료 증거: 체크박스 전체, list/detail contract·route·state test와 검증 기록.

  • 범위 밖: Series 생성·수정·비활성화와 Audio 연결·해제·전체 순서 저장.

  • list가 page, size만 보내고 data.totalCount/items를 소비하며 활성 query·client 활성 filter 없이 loading·empty·error·retry를 제공하는 test를 작성한다.

  • Series detail route의 직접 진입과 새로고침에서 같은 resource를 복원하는 test를 작성한다.

  • 목록의 enum field와 상세의 표시용 publishedDaysOfWeek, genre, keywords, 한국어 state 문자열을 각 응답 DTO 그대로 표시하고 서로 역변환하지 않는 test를 작성한다.

  • 제공된 목록·상세 계약만 mock handler로 만들고 loading·empty·error·retry와 직접 링크 조회를 확인한다.

Task 5.3 Series 생성·수정·비활성화

Goal 실행 P5-T3: 계약 제공 후 Series form, enum·요일·image와 soft delete 규칙을 완성한다.

  • 시작 조건: P5-T2 완료. genre lookup은 legacy GET /creator-admin/audio-content/series/genre를 사용하고, 직접 링크 수정 form 초기화를 위한 상세 응답의 genreId·요일 enum·state enum이 제공됨.

  • 완료 증거: 체크박스 전체, contract/form/image/deactivate test와 검증 기록.

  • 범위 밖: Audio 연결·해제와 전체 순서 저장.

  • enum은 PROCEEDING | SUSPEND | COMPLETE, 요일은 SUN~SAT | RANDOM만 허용한다.

  • 생성 multipart에 필수 imagerequest가 있고 request의 필수 title, introduction, publishedDaysOfWeek, keyword를 보내며 state, isActive, keywords가 없음을 test한다.

  • 수정에서 state 미선택은 key 생략, 선택은 유효 enum만 전송하고 null은 보내지 않는다.

  • RANDOM은 단독, 실제 요일은 하나 이상이어야 하는 schema·UI test를 작성한다.

  • OpenAPI 2.3.0 listAiCharacterSeriesGenres로 장르 option을 조회하고 유효한 genreId만 전송하는 Combobox를 test한다. OpenAPI binding 기본값 0은 선택값으로 허용하지 않는다.

  • Series image JPEG/PNG·10MB, 210:297, height=round(width×297÷210), 최대 1000×1414, 1px 오차, no-upscale을 test한다.

  • 상세 응답의 genreId·요일 enum·state enum으로 직접 링크 form을 초기화하고 상세의 표시용 문자열을 update enum/ID로 역변환하지 않는 test를 작성한다.

  • create-only keyword를 수정 화면에서 읽기 전용으로 표시하고 update payload에 보내지 않는 test를 작성한다.

  • create/update/soft delete의 data=null을 처리하고 일반 update와 soft delete의 isActive 규칙, 목록 재조회·이동·toast를 test한다.

  • mock store로 Series CRUD 후 list enum과 detail 표시 문자열의 서로 다른 DTO가 일관되게 갱신되는 UI를 확인한다.

  • 초기 Series form을 실제 페이지에서 확인한 뒤 title, introduction, keyword, writer, studio, publishedDaysOfWeek의 최대 길이·개수 권고안을 작성한다. backend 호환 확인 전에는 상한을 구현하지 않는다.

Task 5.4 Audio 연결·해제·전체 순서

Goal 실행 P5-T4: Series Audio 연결·해제와 서버가 반환한 Series 전체 순서를 안전하게 관리한다.

  • 시작 조건: P5-T1, P5-T2 완료.

  • 완료 증거: 체크박스 전체, link/unlink/reorder contract·interaction test, 충돌 보존, OQ-009 결정 기록.

  • 범위 밖: 계약 없는 active-only·도메인별 충돌 동작의 추정 구현.

  • 현재 연결 Audio 목록은 page/sizedata.totalCount/items를 사용하고 제공되지 않은 search query를 보내지 않으며 상세 cache를 동기화한다.

  • 후보는 GET .../contents/search?search_word=... 결과만 사용하고 이미 연결된 항목을 중복 선택하지 않는다.

  • 연결 POST는 { contentIdList }, 해제 DELETE는 body 없음임을 contract test로 고정한다.

  • 연결 해제 전 대상 title과 영향을 AlertDialog로 확인한다.

  • 순서 mode는 totalCount와 page/size로 Series 전체를 읽고 최종 순서의 모든 ID를 { ids }로 한 번에 보낸다.

  • drag-and-drop과 동일한 결과를 keyboard·위/아래 button으로 만들 수 있는 test를 작성한다.

  • server의 누락 ID·동시 충돌 오류에서 기존 화면 순서를 보존하고 재조회/재시도 안내를 제공한다.

  • 초기 연결·순서 UI를 실제 페이지에서 확인한 뒤 contentIdList, ids의 최대 개수 권고안을 작성한다. backend 호환 확인 전에는 상한을 구현하지 않는다.

  • mock handler가 연결·해제와 전체 순서 payload를 검증하고 store 결과를 반영하는 E2E를 작성한다.

Task 5.5 Series 반응형·접근성

Goal 실행 P5-T5: Series viewport capability와 활성 범위의 form·연결·정렬 접근성을 검증한다.

  • 시작 조건: P5-T2P5-T3~P5-T4 중 계약이 제공된 활성 범위 완료.

  • 완료 증거: 체크박스 전체, 320px·keyboard·200% zoom·axe E2E 기록.

  • 범위 밖: 모바일 CRUD·연결·순서 mutation.

  • mobile은 목록·상세·연결 콘텐츠 조회만 제공하고 CRUD·연결·순서 action을 route capability로 차단한다.

  • desktop/tablet에서 전체 관리 흐름을 제공한다.

  • keyboard-only 연결·정렬, 320px 조회, 200% zoom, axe critical·serious 0건을 확인한다.

  • P5-T3 genre lookup과 상세 수정용 원본값 제공 후 keyboard-only 요일·genre/form 접근성을 확인한다.

Phase 5 Gate

Goal 실행 P5-GATE: Series mock UI journey와 실제 server integration 상태를 분리해 판정한다.

  • 시작 조건: P5-T1~P5-T5 완료. 외부 계약 때문에 실행하지 않은 범위는 대기/제외 상태와 재개 조건이 기록됨.
  • 완료 증거: 아래 명령과 Expected 통과, 외부 의존 상태와 Phase 검증 기록.
  • 범위 밖: 실패와 무관한 Community/FanTalk 구현.
npm run test:run -- src/features/series
npm run e2e:mock -- tests/e2e/series.spec.ts
npm run e2e -- tests/e2e/series.spec.ts
npm run typecheck
npm run lint
npm run build

Expected: 현재 제공된 계약으로 목록·상세 조회와 contentIdList 연결/해제, ids 전체 reorder UI가 통과한다. genre lookup과 상세 수정용 원본값 제공 후에는 필수 image와 keyword로 Series 생성 → 수정 → soft delete UI까지 통과하고 잘못된 enum·keywords/contentIds/seriesIds payload가 생성되지 않는다. 미제공 계약 범위는 대기로 유지하되 완료된 조회·연결·순서 상태를 되돌리지 않는다.

수동 확인: 현재 계약으로 320px 조회와 desktop/tablet 연결·keyboard reorder를 확인한다. genre lookup과 상세 수정용 원본값 제공 후 생성·수정 form도 확인하고, 표시용 상세 문자열이 update payload로 역변환되지 않는지 network에서 본다.

Task R5.1 — Series partial update 불변식 복구

Goal 실행 P5-R1: Series update에서 변경 없는 state를 생략하고 일반 update와 soft delete schema를 분리한다.

  • 연결 리뷰: Phase 5 재리뷰REV-P5-001, REV-P5-002

  • 시작 조건: SERIES-002, SERIES-004, SERIES-010.

  • Files: Modify: src/features/series/components/SeriesForm.tsx, src/features/series/schemas/series-schema.ts, src/features/series/api/series-api.ts, src/features/series/tests/series-form.test.tsx, src/features/series/tests/series-contract.test.ts.

  • Interfaces: unchanged state는 key 생략, changed state는 3개 enum만 허용; 일반 update에 isActive 없음, deactivate는 {isActive:false} 전용.

  • 범위 밖: Series state enum 추가, backend partial update 의미 변경.

  • RED: unchanged/changed state payload와 state:null, isActive:true/null 거부 test를 작성한다.

  • GREEN: 원본 비교 serializer와 일반/deactivate schema를 분리한다.

  • REFACTOR: API helper가 전용 schema만 호출하도록 정리하고 Series 전체 회귀를 실행한다.

  • 실행 명령: npm run test:run -- src/features/series.

  • 기대 결과: exit 0, unchanged state request 0건, isActive=true/null parse 0건, deactivate false만 통과.

  • 수동 확인: edit에서 state 미변경/변경 저장 network payload를 각각 확인한다.

Task R5.2 — Series image format pair 검증

Goal 실행 P5-R2: Series JPEG/PNG extension↔MIME mismatch를 crop 전에 거부한다.

  • 연결 리뷰: Phase 5 재리뷰REV-P5-003

  • 시작 조건: FILE-002, FILE-005, SERIES-014.

  • Files: Modify: series-image-policy.ts, series-contract.test.ts, series-form.test.tsx.

  • Interfaces: .jpg/.jpeg ↔ image/jpeg, .png ↔ image/png; size/aspect/no-upscale는 유지.

  • 범위 밖: 실제 image sniffing, crop 계산(P1-R7).

  • RED: .png+image/jpeg, .jpg+image/png 거부와 crop source 호출 0건 test를 작성한다.

  • GREEN: extension별 MIME mapping을 policy에 적용한다.

  • REFACTOR: Audio/Character policy와 불필요한 범용 abstraction 없이 message를 정리한다.

  • 실행 명령: npm run test:run -- src/features/series src/shared/validation/file-media-policy.test.ts.

  • 기대 결과: exit 0, mismatch upload/crop 0건, 기존 10MiB/ratio test 통과.

  • 수동 확인: 잘못된 pair 선택 시 즉시 field 오류가 보이는지 확인한다.

Task R5.3 — Series 표시용 label 일관성 복구

Goal 실행 P5-R3: Series 목록·상세에서 요일·state·boolean·genre를 운영자용 label로 표시하고 원본 enum/ID는 mutation에만 유지한다.

  • 연결 리뷰: Phase 5 현재 리뷰REV-P5-004

  • 시작 조건: SERIES-007, SERIES-015, PRD §13 중앙 formatter 기준과 장르 목록 계약.

  • 완료 증거: raw enum/boolean/field-name 노출 실패 test, 표시 계층 formatter 적용, Series focused unit·mock E2E·Phase Gate와 검증 기록.

  • Files: Modify: SeriesListPage.tsx, SeriesList.tsx, SeriesListItem.tsx, SeriesSummary.tsx, 관련 tests와 tests/e2e/series.spec.ts; 필요 시 Series 전용 formatter.

  • Interfaces: SUN..SAT/RANDOM, PROCEEDING/SUSPEND/COMPLETE, boolean과 genreId를 한국어 label로 표시하되 API DTO와 create/update payload는 원본 값을 유지한다.

  • 범위 밖: OpenAPI enum 변경, 장르 ID 역변환, 새 전역 i18n framework.

  • RED: desktop/mobile 목록과 상세에 SUN, PROCEEDING, isAdult true, isActive true, genreId가 사용자 문구로 노출되는 실패 test를 작성한다.

  • GREEN: 장르 응답과 Series 전용 formatter로 표시 label을 만들고 payload test는 원본 enum/ID를 계속 확인한다.

  • REFACTOR: SeriesSummary의 중복 map을 재사용 가능한 Series 표시 helper로만 정리한다.

  • 실행 명령: npm run test:run -- src/features/series; npm run e2e:mock -- tests/e2e/series.spec.ts; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, Series 사용자 화면의 raw enum·boolean·field-name 노출 0건, mutation payload 변경 0건.

  • 수동 확인: 320/768/1280px에서 목록·상세 label과 수정 form의 원본 선택값을 확인한다.

Task R5.4 — Series 생성·수정 입력 schema 불변식 복구

Goal 실행 P5-R4: Series 생성의 유효 장르 필수 조건과 생성·수정 요일 배열의 유일성을 schema·mock·contract test에서 강제한다.

  • 연결 리뷰: Phase 5 현재 리뷰REV-P5-005

  • 시작 조건: SERIES-006~007, OpenAPI SeriesCreateRequest.publishedDaysOfWeek.uniqueItems=true, binding 기본값 genreId=0은 domain에서 무효라는 설명.

  • 완료 증거: 누락 genreId와 중복 요일의 실패 test, schema·mock 최소 수정, Series focused unit·mock E2E·정적 Gate 통과.

  • Files: Modify: src/features/series/schemas/series-schema.ts, src/features/series/tests/series-contract.test.ts, src/features/series/tests/series-form.test.tsx, src/shared/mocks/series-mock-store.ts; 필요 시 Series mock handler test.

  • Interfaces: create genreId는 positive integer 필수, publishedDaysOfWeek는 중복 없는 실제 요일 하나 이상 또는 RANDOM 단독이며 update의 non-null 요일에도 같은 유일성을 적용한다.

  • 범위 밖: 장르 목록 자체의 유효성 backend 재검증, 요일 최대 개수 추가, OpenAPI JSON 변경.

  • RED: seriesCreateRequestSchemagenreId 누락과 ["MON","MON"]을 거부하고 update가 중복 요일을 거부해야 한다는 실패 test를 작성했다.

  • GREEN: create genreId를 필수로 바꾸고 공용 요일 schema에 uniqueness를 적용하며 mock의 임의 첫 장르 fallback을 제거했다.

  • REFACTOR: UI의 기존 장르·요일 검증을 schema 불변식과 같은 용어로 정리하되 중복 validation framework는 만들지 않았다.

  • 실행 명령: npm run test:run -- src/features/series src/shared/mocks; npm run e2e:mock -- tests/e2e/series.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 누락/0 장르와 중복 요일 request 성공 0건, 기존 유효 생성·수정 회귀 0건.

  • 수동 확인: Series 생성에서 장르 미선택·요일 중복 전송이 불가능하고 정상 생성은 유지되는지 확인한다.

P5-R4 수정 검증 기록 (2026-07-30):

  • RED: npm run test:run -- src/features/series/tests/series-contract.test.ts — 1 failed / 6 passed. seriesCreateFormSchemagenreId 누락 payload를 성공 처리해 기대대로 실패했다.
  • GREEN focused: npm run test:run -- src/features/series/tests/series-contract.test.ts src/features/series/tests/series-form.test.tsx — 2 files / 14 tests passed. 생성 genreId 필수, 생성·수정 중복 요일 거부, 기존 유효 생성·수정 form 회귀를 확인했다.
  • REFACTOR/회귀: npm run test:run -- src/features/series src/shared/mocks — 13 files / 51 tests passed. npm run typecheck, npm run lint, npm run build — 모두 exit 0.
  • LSP diagnostics: src/features/series/schemas/series-schema.ts, src/shared/mocks/series-mock-store.ts, src/features/series/tests/series-contract.test.ts — 오류 0건.
  • E2E/수동 확인: 사용자 지시에 따라 개발 중 E2E는 실행하지 않고 P10-R5 이후 최종 E2E에서 확인한다.

Task R5.5 — Series image crop 준비 경계 보강

Goal 실행 P5-R5: Series 생성·수정에서 image crop source 준비와 연속 선택을 추적하고 crop 확정 전 저장을 차단한다.

  • 연결 리뷰: Phase 5 현재 리뷰REV-P5-006

  • 시작 조건: FILE-005, FILE-007~009, FILE-012, FILE-015와 Series image 생성 필수/수정 optional 계약.

  • 완료 증거: 준비 중 edit 저장·stale source·준비 실패를 재현하는 실패 test, staged selection 최소 수정, Series focused unit·mock E2E·file Gate 통과.

  • Files: Modify: src/features/series/components/SeriesForm.tsx, 관련 Series form/file tests와 tests/e2e/series.spec.ts.

  • Interfaces: image는 crop 적용 결과만 보유하고 준비/crop 중 submit을 비활성화·guard한다. 마지막 선택만 유효하며 취소는 기존 서버 image를 유지하고 준비 실패를 inline 표시한다.

  • 범위 밖: Series crop ratio/output 계산 변경, 새 범용 upload state library, API multipart part 변경.

  • RED: edit에서 source 준비 중 저장, 느린 첫 선택이 빠른 둘째 선택을 덮는 경우, source 준비 reject를 실패 test로 고정한다.

  • GREEN: image selection token·준비 상태·오류 처리와 submit guard를 추가한다.

  • REFACTOR: create/edit 공통 SeriesForm 내부에서 lifecycle을 끝내고 도메인 밖 abstraction을 추가하지 않는다.

  • 실행 명령: npm run test:run -- src/features/series; npm run e2e:mock -- tests/e2e/series.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 준비/crop 미확정 image의 저장 0건, stale dialog 0건, 정상 210:297 crop result만 multipart에 포함.

  • 수동 확인: 기존 image가 있는 수정 화면에서 느린 파일 연속 선택·취소·적용·즉시 저장을 반복해 마지막 의도만 반영되는지 확인한다.

Progress — 2026-07-31 P5-R5 완료

  • RED: npm run test:run -- src/features/series/tests/series-form.test.tsx에서 준비 중 저장 버튼 미비활성화, preview reject inline 오류 부재, unhandled rejection을 확인했다.
  • GREEN: SeriesForm에 image selection token, isImagePreparing, 준비/crop 중 submit guard, preview reject inline 오류를 추가했다. image는 crop 적용 결과만 commit하고, 수정 화면 crop 취소는 기존 서버 image 유지 계약을 보존한다. focused 재실행 결과 series-form.test.tsxseries-form-crop.test.tsx 2 files / 13 tests passed였다.
  • REFACTOR/회귀: crop lifecycle test를 series-form-crop.test.tsx로 분리해 SeriesForm.tsx 220 LOC, series-form.test.tsx 230 LOC, series-form-crop.test.tsx 85 LOC로 유지했다. npm run test:run -- src/features/series 결과 8 files / 37 tests passed였다. 사용자 지시에 따라 mock E2E는 전체 Task 완료 전까지 보류했다. npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0 또는 no output이었다. Series directory LSP diagnostics는 17 TSX files / 오류 0건이었다.

Task R5.6 — Series 생성 form 이미지·keyword 입력 UX 정렬

Goal 실행 P5-R6: Series 생성 form의 image 입력을 최상단에 배치하고 Audio tags와 동일한 공용 TagInput으로 keyword를 입력하되 기존 comma-separated request 계약을 유지한다.

  • 시작 조건: P4-R10, P5-R5, SERIES-014, 사용자 승인 설계.

  • 완료 증거: image-first field order와 Enter/comma chip·remove 동작의 실패 test, 공용 TagInput 재사용, Series comma-separated keyword request, Audio tag 회귀, Chromium mock browser와 시각 QA 증거.

  • Files: Move/Modify: src/features/audio-contents/components/AudioTagInput.tsxsrc/shared/ui/tag-input.tsx; Modify: src/features/audio-contents/components/AudioContentForm.tsx, src/features/series/components/SeriesForm.tsx, src/features/series/tests/series-form.test.tsx, src/shared/ui/__tests__/tag-input.test.tsx, tests/e2e/series.spec.ts, DESIGN.md, docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md.

  • Interfaces: TagInput({label,value,onChange,error,errorId})는 comma-separated string을 consume/emit하고 Enter/comma로 non-empty trimmed chip을 확정하며 remove button으로 한 chip만 삭제한다. Series create는 keyword, Audio create/update는 tags field명을 그대로 유지한다.

  • 범위 밖: Series 수정·상세 keyword, duplicate/max-count 정책, API schema·endpoint·multipart part 변경, 새 dependency.

  • RED: Series image-first order와 keyword chip→"달빛,상담" request, 공용 input의 blank/remove 동작 실패 test를 작성하고 의도한 실패를 확인했다.

  • GREEN: AudioTagInput을 공용 TagInput으로 이름·위치를 정리하고 Audio/Series에서 재사용하며 Series FileField를 form 첫 field로 이동했다.

  • REFACTOR: feature 간 역참조와 중복 chip 구현이 없는지 확인하고 focused·전체 회귀, typecheck·lint·build, Chromium mock E2E와 시각 QA를 실행했다.

  • 실행 명령: npm run test:run -- src/shared/ui/__tests__/tag-input.test.tsx src/features/series/tests/series-form.test.tsx src/features/audio-contents/tests/audio-form.test.tsx; npm run test:run; npm run typecheck; npm run lint; npm run build:prod; npm run e2e:mock -- tests/e2e/series.spec.ts --project=chromium.

  • 기대 결과: 모든 명령 exit 0, Series image-first order와 keyword="달빛,상담", Audio tags="상담,힐링", 빈 chip 0건, 삭제 대상 외 chip 손실 0건, API 계약 변경 0건.

  • 수동 확인: Chromium 1280px Series 생성 화면에서 image가 첫 입력이고 keyword chip 추가·삭제 및 Korean text clipping/overflow가 없는지 확인한다.

P5-R6 완료 Progress — 2026-08-04:

  • RED: shared TagInput import 부재, Series image가 title 뒤에 있는 DOM 순서, plain keyword input이 첫 값을 덮어 "상담"만 보내는 실패를 확인했다. validation focus 정렬은 image 대신 title이 focus되는 실패로, FileField visible focus는 focus-within ring class 부재 실패로 각각 고정했다.
  • GREEN: AudioTagInputsrc/shared/ui/tag-input.tsx의 공용 TagInput으로 이동·일반화하고 Audio tags와 Series keyword가 같은 chip UI를 사용하게 했다. Series image를 form 첫 field로 이동하고 request는 keyword: "달빛,상담" comma-separated string을 유지했다. image-first validation focus가 보이도록 공용 FileField 카드에 기존 ring token의 focus-within 상태만 추가했다.
  • REFACTOR/회귀: focused 3 files / 20 tests와 FileField 포함 focused 3 files / 15 tests를 통과했다. 최종 npm run test:run은 82 files / 437 tests, npm run typecheck, npm run lint, npm run build:prod, git diff --check는 모두 통과했고 build에는 기존 500kB chunk warning만 남았다. npm run e2e:mock -- tests/e2e/audio-content.spec.ts tests/e2e/series.spec.ts --project=chromium은 13 tests passed였다.
  • 수동·시각 QA: Playwright Chromium 768×900·1280×900에서 image-first, Enter/comma chip 2개, remove 후 나머지 값, 빈 draft, 가로 overflow 0건을 확인했다. validation 후 sr-only file input focus와 visible cyan FileField ring을 768×900에서 확인했다. 독립 visual functional/CJK reviewer와 고엄격 diff reviewer의 최종 판정은 모두 PASS/APPROVE, blocker 0건이었다. Mock Preview sticky banner 아래로 static header가 이동한 프레임은 scrollY=47의 정상 문서 스크롤로 측정했고 scrollY=0에서 동일 focus/ring과 온전한 header를 재확인했다.

Phase 6. Community vertical slice

목표: 별도 상세 route/GET 없이 Community 목록과 Sheet만으로 게시글 등록·조회·수정·고정·비활성화·첨부 재생을 완료한다.

Phase Goal P6: Task 6.1 → 6.4와 Phase 6 Gate로 목록 기반 Community Sheet·media slice를 완성한다.

  • 시작 조건: P2-GATEP4-T2 player integration 완료.
  • 완료 조건: P6-T1~P6-T4, P6-GATE 완료. 오류·price는 제공 계약 또는 최소 규칙으로 종결.
  • 실행 순서: 계약 확인 → 목록/Sheet → form/media → 반응형·접근성.

요구사항: COMMUNITY-001~015, FILE-001~004, FILE-007~009, FILE-011~014, PRD 9의 Community 범위.

외부 의존: EXT-007 active-only 반환 보장, EXT-008 pagination의 total/hasNext 또는 종료 규칙. price 상한은 99999이고, 파일 용량·MIME 등 backend 동일 검증은 확인됐다. OpenAPI 밖의 Community 오류 message key는 분기하지 않는다. Comments는 Phase 8에서 연결한다.

주요 Files:

  • Create: src/features/community-posts/api/community-post-api.ts
  • Create: src/features/community-posts/model/types.ts
  • Create: src/features/community-posts/validation/community-post-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,community-list.test.tsx,community-form.test.tsx,community-sheet.test.tsx,community-test-support.ts}
  • Create: tests/e2e/community.spec.ts

Phase 6 Task 실행 계약

Goal Files Interfaces TDD·검증 기준
P6-T1 Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md; Read: docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json; Test: 없음 Consumes: CommunityPost* schema·3 operation. Produces: list/create/update contract map, 외부 의존과 Sheet inventory TDD 예외: 외부 계약 조사 Task다. OpenAPI JSON parse와 rg -n 'COMMUNITY-01[2-5]' 문서 추적 검사를 실행한다. 기대 exit 0. 수동 확인: timezone·배열 data·null mutation·미제공 pagination이 분리된다.
P6-T2 Create: src/features/community-posts/api/community-post-api.ts, src/features/community-posts/model/types.ts, src/features/community-posts/pages/CommunityPostListPage.tsx, src/features/community-posts/components/{CommunityPostList,CommunityPostListItem,CommunityPostSheet}.tsx, src/features/community-posts/tests/{community-contract.test.ts,community-list.test.tsx,community-sheet.test.tsx} Consumes: CommunityPostListApiResponse, update multipart. Produces: getCommunityPosts({characterId,timezone,page,size}), updateCommunityPost, collection Sheet cache policy TDD 적용: npm run test:run -- src/features/community-posts/tests/community-contract.test.ts src/features/community-posts/tests/community-list.test.tsx src/features/community-posts/tests/community-sheet.test.tsx; 기대 exit 0. 수동 확인: detail GET 0회, pin/deactivate 후 refetch.
P6-T3 Create: src/features/community-posts/validation/community-post-media-policy.ts, src/features/community-posts/components/CommunityPostForm.tsx, src/features/community-posts/tests/community-form.test.tsx, src/features/community-posts/tests/community-test-support.ts, tests/e2e/community.spec.ts; Modify: src/features/community-posts/api/community-post-api.ts, src/features/community-posts/components/CommunityPostSheet.tsx, src/features/community-posts/tests/{community-contract.test.ts,community-sheet.test.tsx}, src/shared/mocks/{audio-content-fixtures.ts,community-post-fixtures.ts,community-post-mock-store.ts}, playwright.config.ts Consumes: CommunityPostCreateRequest, CommunityPostUpdateRequest, multipart part names. Produces: createCommunityPost, form serializer와 media policy TDD 적용: npm run test:run -- src/features/community-posts src/shared/validationnpm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium; 기대 exit 0. 수동 확인: postImage/audioFile, update audio/price 없음, audioUrl 재생.
P6-T4 Modify: src/features/community-posts/pages/CommunityPostListPage.tsx, src/features/community-posts/components/{CommunityPostList,CommunityPostListItem,CommunityPostForm,CommunityPostSheet}.tsx; Test: tests/e2e/community.spec.ts Consumes: P6-T2/T3 UI. Produces: viewport·Sheet focus capability evidence TDD 적용: npm run e2e:mock -- tests/e2e/community.spec.ts; 기대 지원 project 전부 통과. 수동 확인: 320px, focus trap/복귀, 200% zoom, axe.

P6-T2~P6-T4는 각 row의 focused test로 RED → GREEN → REFACTOR를 실행하고 관련 feature test·typecheck·lint 결과와 수동 확인을 §7 검증 기록에 누적한다.

Task 6.1 Phase 계약 확인

Goal 실행 P6-T1: Community 오류·media·price 계약, mock scenario와 목록/Sheet component map을 확정한다.

  • 시작 조건: P2-GATE, P4-T2 완료, PRD COMMUNITY-001~015, MOCK-001~009와 OpenAPI Community 3개 operation/schema 확인.

  • 완료 증거: 체크박스 전체, contract fixture와 상태/action inventory의 세 문서 일치.

  • 범위 밖: price 상한·오류 key 추정과 Comments 구현.

  • OpenAPI 공통 오류 status·shape를 fixture에 기록하고 Community 전용 message key와 media upload 오류 계약은 미제공으로 표시한다. 정확한 fixture를 추정하지 않는다.

  • price 최대값이 제공되면 Audio와 같은 정책으로 갱신하고, 없으면 0 이상 정수만 유지한다.

  • 목록 필수 timezone, page/size, 배열 data, 생성 audioFile/postImage/request, 수정 postImage/request, mutation data=null을 contract fixture로 고정한다.

  • 목록에 total/hasNext와 isActive/fixedAtUtc가 없음을 기록하고 해당 값을 fixture에서 추가하지 않는다.

  • 목록·Sheet·form/media의 상태/action inventory를 작성하고 Page는 collection query/policy 조합, feature component는 Community 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.

  • Community active list, Sheet, pin, media와 CRUD의 deterministic browser fixture 시나리오를 확정한다.

Task 6.2 목록·collection Sheet

Goal 실행 P6-T2: 전용 detail route/GET 없는 목록과 collection Sheet mutation 흐름을 완성한다.

  • 시작 조건: P6-T1 완료.

  • 완료 증거: 체크박스 전체, list/Sheet/router/cache/pin/deactivate test와 detail GET 0회 기록.

  • 범위 밖: 댓글과 제공 계약에 없는 Community 검색.

  • 목록이 필수 timezone=Asia/Seoulpage/size를 보내고 배열 data를 소비하며 loading·empty·error·retry와 URL query 보존을 제공하는지 test한다. 제공 계약에 없는 Community search, total, hasNext를 만들지 않는다.

  • 목록 item을 source로 Sheet를 열고 전용 detail GET을 0회 호출하는 test를 작성한다.

  • /community-posts/:postId, /edit route가 존재하지 않는 router test를 작성한다.

  • Sheet의 조회·수정·고정/해제·비활성화가 목록 cache와 같은 server response를 사용한다.

  • 고정/해제 후 mutation data=null을 처리하고 목록을 재조회해 isFixed를 갱신한다. 계약에 없는 fixedAtUtc는 표시하지 않는다.

  • soft delete request가 isActive=false, isFixed=false를 보내고 성공 data=null을 처리하는 contract test를 작성한다.

  • soft delete 성공 시 Sheet 종료, 목록 재조회와 성공 toast를 확인한다. 비활성 항목 제거는 active-only 계약 제공 후 server mode에서 검증한다.

  • mock mode도 전용 detail GET 없이 list store만으로 Sheet와 pin/deactivate 최종 UI를 갱신한다.

Task 6.3 게시글 form·첨부 media

Goal 실행 P6-T3: Community form, JPEG/PNG/GIF와 audio 첨부·재생 규칙을 완성한다.

  • 시작 조건: P6-T1, P6-T2 완료.

  • 완료 증거: 체크박스 전체, serializer/form/media/file-boundary/player test, OQ-009 결정 기록.

  • 범위 밖: GIF 재인코딩, URL 갱신 전용 요청, Comments.

  • 생성 payload에 isActive가 없고 일반 update/soft delete가 공통 isActive 규칙을 지키는 test를 작성한다.

  • create의 필수 content·isCommentAvailable·isAdult, optional price와 multipart postImage/audioFile을 test하고 생성 request에 isFixed가 없음을 고정한다.

  • update는 optional postImage와 content·isCommentAvailable·isAdult·isActive·isFixed만 보내며 price·audioFile 교체 UI/request가 없음을 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 4와 동일하게 조합해 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으로 목록이 정상 재조회된 때만 새 audioUrl을 사용한다.

  • 초기 Community Sheet/form을 실제 페이지에서 확인한 뒤 content의 최대 길이 권고안을 작성한다. backend 호환 확인 전에는 상한을 구현하지 않는다.

  • mock handler가 multipart contract를 검증하고 local preview media asset으로 create/update/play 최종 UI를 재현한다.

Task 6.4 Community 반응형·접근성

Goal 실행 P6-T4: Community viewport capability와 Sheet/media/form 접근성을 검증한다.

  • 시작 조건: P6-T2, P6-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 6 Gate

Goal 실행 P6-GATE: Community mock UI journey와 실제 server integration 상태를 분리해 판정한다.

  • 시작 조건: P6-T1~P6-T4 완료.
  • 완료 증거: 아래 명령과 Expected 통과 및 Phase 검증 기록.
  • 범위 밖: 실패와 무관한 FanTalk/Comments 구현.
npm run test:run -- src/features/community-posts src/shared/validation
npm run e2e:mock -- tests/e2e/community.spec.ts
npm run e2e -- tests/e2e/community.spec.ts
npm run typecheck
npm run lint
npm run build

Expected: mock mode에서 timezone 목록 → postImage/audioFile/request create → 목록 item Sheet 조회/허용 field 수정 → pin/unpin → audioUrl 재생 → soft delete 요청 UI가 통과하고 Community detail GET·detail route·수정 audio/price request는 0건이다. pagination 종료와 active-only 결과는 계약 제공 전 완료로 주장하지 않는다.

수동 확인: 320px와 desktop/tablet에서 목록·Sheet·첨부 재생·form을 keyboard-only로 확인하고, detail GET·수정 audio/price 요청이 없으며 focus가 Sheet trigger로 복귀하는지 본다.

Task R6.1 — Community raw price validation 복구

Goal 실행 P6-R1: Community create에서 음수·소수 가격을 다른 값으로 바꾸지 않고 제출 전에 거부한다.

  • 연결 리뷰: Phase 6 재리뷰REV-P6-001

  • 시작 조건: COMMUNITY-007, CAN_PRICE_MAX=99999; Audio와 의미가 같은 raw-price 규칙은 재사용한다.

  • Files: Modify: community-post-form-helpers.ts, CommunityPostForm.tsx, community-form.test.tsx, community-contract.test.ts, 필요 시 src/shared/validation/can-price.ts.

  • Interfaces: group separator와 suffix만 display normalization; -, .가 포함된 raw input은 invalid.

  • 범위 밖: Community update price UI, 통화 locale 확장.

  • RED: -1, 1.5 입력 시 inline 오류와 POST 0건, 0/99999 허용 test를 작성한다.

  • GREEN: raw/display price state 또는 공통 parser result를 적용한다.

  • REFACTOR: Audio P4-R1과 동일 의미의 validation만 shared로 올리고 Community 회귀를 실행한다.

  • 실행 명령: npm run test:run -- src/shared/validation/can-price.test.ts src/features/community-posts.

  • 기대 결과: exit 0, invalid price POST 0건, 경계값 multipart 유지.

  • 수동 확인: Community create 가격 input에서 음수·소수 오류 문구와 입력 보존을 확인한다.

Task R6.2 — Community 상태 boolean label 정리

Goal 실행 P6-R2: Community 목록의 성인 여부를 raw boolean이 아닌 한국어 상태 label로 표시한다.

  • 연결 리뷰: Phase 6 현재 리뷰REV-P6-002

  • 시작 조건: PRD §13 날짜·가격·상태 label 중앙 표시 기준과 기존 desktop/mobile 목록.

  • 완료 증거: 성인 true/false 노출 실패 test, 최소 표시 수정, Community focused unit·mock E2E와 검증 기록.

  • Files: Modify: CommunityPostList.tsx, CommunityPostListItem.tsx, community-list.test.tsx, tests/e2e/community.spec.ts.

  • Interfaces: boolean은 성인/일반처럼 의미 있는 한국어 label로 변환하고 API DTO·multipart 값은 boolean을 유지한다.

  • 범위 밖: Community API schema 변경, 상태 filter 추가, 전역 i18n framework.

  • RED: desktop/mobile에 raw true/false가 표시되지 않고 핵심 상태 label이 일치하는 실패 test를 작성한다.

  • GREEN: Community 전용 상태 formatter를 목록 row/card에 적용한다.

  • REFACTOR: 고정·댓글 허용·성인 상태 copy를 한 표시 helper로 정리한다.

  • 실행 명령: npm run test:run -- src/features/community-posts; npm run e2e:mock -- tests/e2e/community.spec.ts; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, Community 사용자 화면의 raw boolean 0건, request payload 변경 0건.

  • 수동 확인: desktop과 320px 카드에서 동일 게시글의 고정·댓글·성인 상태를 확인한다.

Task R6.3 — Community 비활성화 확인 경계 추가

Goal 실행 P6-R3: Community 게시글 비활성화를 직접 실행하지 않고 영향 설명이 있는 확인 dialog를 거쳐 확정하도록 한다.

  • 연결 리뷰: Phase 6 현재 리뷰REV-P6-003

  • 시작 조건: PRD §10.4의 영향이 큰 비활성화 동작 AlertDialog 기준과 COMMUNITY-005 soft delete 계약.

  • 완료 증거: 첫 click에서 DELETE 0건·confirm 후 1건을 기대하는 실패 test, 공통 확인 UI 재사용, Community focused unit·mock E2E·접근성 Gate 통과.

  • Files: Modify: src/features/community-posts/components/CommunityPostSheet.tsx, src/features/community-posts/tests/community-sheet.test.tsx, tests/e2e/community.spec.ts; Reuse: src/shared/ui/confirm-deactivate-dialog.tsx.

  • Interfaces: 확인 전 request 0건, confirm 후 현재 post ID에 DELETE 1건, pending/error와 Sheet focus 경계를 접근 가능하게 유지한다.

  • 범위 밖: 댓글 삭제 확인 정책 변경, 복원 기능, 새 dialog primitive.

  • RED: 비활성화 첫 click은 확인 dialog만 열고 DELETE를 보내지 않으며 취소는 Sheet로 focus를 돌리고 confirm만 DELETE 1건을 보내야 한다는 실패 test를 작성한다.

  • GREEN: 기존 ConfirmDeactivateDialog를 Community Sheet에 연결하고 pending·오류 상태를 전달한다.

  • REFACTOR: 삭제 API와 성공 callback은 유지하고 dialog open/pending state만 Sheet에 추가한다.

  • 실행 명령: npm run test:run -- src/features/community-posts; npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 확인 전·취소 후 DELETE 0건, confirm 연타에도 DELETE 최대 1건, 실패 안내와 재시도 가능.

  • 수동 확인: keyboard-only로 Sheet의 비활성화 → 취소 → 재확인 → 실패/성공 흐름과 focus 복귀를 확인한다.

Task R6.4 — Community GIF crop source Blob URL 해제

Goal 실행 P6-R4: Community GIF width 검사를 위해 생성한 crop source의 Blob URL을 성공·거부 경로에서 각각 정확히 한 번 해제한다.

  • 연결 리뷰: Phase 6 현재 리뷰REV-P6-004

  • 시작 조건: PRD FILE-003, FILE-011, FILE-014, prepareCommunityPostImage()가 GIF의 width를 읽은 뒤 source.release 소유권을 form에 전달하지 않고 ready 또는 error를 반환하는 현재 구조.

  • 완료 증거: 800px 이하 GIF ready와 800px 초과 GIF error에서 release 1회, JPEG/PNG crop handoff에서 조기 release 0회를 고정하는 실패 test, Community focused unit·mock E2E·정적 Gate 기록.

  • Files: Modify: src/features/community-posts/validation/community-post-media-policy.ts; Create: src/features/community-posts/tests/community-post-media-policy.test.ts; Test: src/features/community-posts/tests/community-form.test.tsx, src/features/community-posts/tests/community-sheet.test.tsx, tests/e2e/community.spec.ts.

  • Interfaces: GIF는 dimension 확인 후 crop source를 외부로 반환하지 않으므로 prepareCommunityPostImage 내부가 release를 소유한다. JPEG/PNG crop 결과의 release 소유권은 기존 form/Sheet에 그대로 전달한다.

  • 범위 밖: GIF resize·crop·re-encode, 외부 image decoder, 공통 crop source 재설계, 서버 GIF 계약 변경.

  • Phase 6 Gate 연결: media policy focused unit, create/Sheet 교체·stale·unmount 회귀, mock Chromium Community journey, typecheck·lint·build가 모두 통과하기 전에는 완료하지 않는다.

  • RED: idempotent release spy를 사용해 valid GIF·oversized GIF은 각 1회, JPEG/PNG crop handoff는 0회인 실패 test를 작성했다.

  • GREEN: GIF width 판정이 끝나면 ready/거부 반환 전 source를 해제하는 최소 수정을 적용했다.

  • REFACTOR: release 호출 경계를 GIF 분기 한 곳에 유지하고 create/Sheet의 JPEG/PNG lifecycle과 stale selection 회귀를 확인했다.

  • RED/GREEN/REFACTOR, media policy·Community 단위·정적 검증을 Progress와 Phase 6 리뷰에 누적했다. E2E와 반복 GIF 수동 확인은 전체 Task 구현 후 필요 시 수행한다.

  • 실행 명령: npm run test:run -- src/features/community-posts/tests/community-post-media-policy.test.ts src/features/community-posts/tests/community-form.test.tsx src/features/community-posts/tests/community-sheet.test.tsx; npm run test:run -- src/features/community-posts src/shared/lib/create-image-crop-source.test.ts; npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, valid/oversized GIF Blob URL 미해제 0건·중복 release 0건, JPEG/PNG crop lifecycle 회귀 0건.

  • 수동 확인: GIF를 반복 교체하고 800px 경계 아래/위를 선택해 crop Dialog 미노출, animation 유지, 거부 문구와 재선택 회복을 확인한다.

P6-R4 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/features/community-posts/tests/community-post-media-policy.test.ts — 1 failed. 신규 prepareCommunityPostImage releases GIF crop sources after width validation에서 valid GIF release 기대 1회 대비 0회로 실패했다.
  • GREEN: prepareCommunityPostImage()의 GIF 분기에서 width 판정을 먼저 저장한 뒤 source.release?.()를 호출하게 했다.
  • REFACTOR/회귀: release 호출은 GIF 분기 한 곳에만 두고 JPEG/PNG crop handoff는 조기 release하지 않도록 유지했으며, valid/oversized GIF 모두 width → release 순서를 test로 고정했다. npm run test:run -- src/features/community-posts/tests/community-post-media-policy.test.ts src/features/community-posts/tests/community-form.test.tsx src/features/community-posts/tests/community-sheet.test.tsx — 3 files / 18 tests passed. npm run test:run -- src/features/community-posts — 7 files / 45 tests passed.
  • 정적 검증: LSP diagnostics community-post-media-policy.ts 오류 0건이고 test 파일은 LSP 응답 timeout이 있었으나 typecheck로 오류 없음을 확인했다. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. build는 기존 500kB chunk warning을 표시했다.
  • 리뷰어 게이트: 1차 review에서 width 판정 전 release 순서 blocker를 지적받아 보완했고, delta review에서 APPROVED 판정을 받았다.
  • E2E/수동: 개발 중 E2E와 반복 GIF browser 수동 확인은 사용자 지시에 따라 전체 Task 구현 후 필요 시 수행한다.

Task R6.5 — 업로드 이미지 미리보기와 Community 생성 media flow 정렬

Goal 실행 P6-R5: 공통 FileField에 실제 업로드 이미지 미리보기를 제공하고 Community 생성 화면을 이미지 → 조건부 오디오 → 내용 순서와 Audio 동일 가격 계약으로 정렬한다.

  • 시작 조건: PRD COMMUNITY-003, COMMUNITY-007, COMMUNITY-013, COMMUNITY-016, FILE-016; 사용자 승인 설계.

  • 완료 증거: 이미지 preview URL 생성·교체·해제, 비이미지 filename-only, Community media 순서·audio 조건부 표시/제거, 기본 가격 0·blank 거부·항상 price 전송의 RED/GREEN, Audio 가격 회귀, Chromium mock browser와 visual QA.

  • Files: Create: src/shared/ui/can-price-field.tsx, src/shared/ui/__tests__/can-price-field.test.tsx; Modify: src/app/protected-admin-shell.tsx, src/app/App.protected-shell.test.tsx, src/shared/ui/file-field.tsx, src/shared/ui/page-state.tsx, src/shared/ui/__tests__/file-field.test.tsx, src/shared/ui/__tests__/page-state.test.tsx, src/features/audio-contents/components/AudioContentForm.tsx, src/features/audio-contents/components/audio-content-form-helpers.ts, src/features/audio-contents/tests/audio-form-create-red.test.tsx, src/features/audio-contents/tests/audio-form-update.test.tsx, src/features/audio-contents/tests/audio-form.test.tsx, src/features/community-posts/components/CommunityPostForm.tsx, src/features/community-posts/components/community-post-form-helpers.ts, src/features/community-posts/tests/community-form.test.tsx, src/features/community-posts/tests/community-price-validation.test.tsx, src/features/community-posts/tests/community-contract.test.ts, tests/e2e/community.spec.ts, DESIGN.md, docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md.

  • Interfaces: FileField는 image File일 때만 object URL preview를 소유하고 교체·제거·unmount에서 revoke한다. CanPriceField({value,onChange,error,errorId})는 Audio와 Community의 native number/min=0/step=1/단위·오류 연결을 공유하고 range validation은 domain submit이 소유한다. Community는 최종 postImage가 있을 때만 audio input을 렌더하며 image 제거·교체 시작 시 audioFile을 제거한다.

  • 범위 밖: Community 수정 Sheet, multipart append 순서, API schema의 optional price, 비이미지 preview, 새 dependency, GIF resize·crop·re-encode.

  • RED: FileField previews image values and revokes object URLs on replacement, clear, and unmount; FileField keeps non-image values filename-only without creating an object URL을 실패시킨다.

  • GREEN: image value에만 bounded object-contain preview를 렌더하고 URL lifecycle을 FileField 한 곳에서 소유한다.

  • RED: CanPriceField renders the Audio numeric CAN price contract; CanPriceField connects its inline error and emits Audio-compatible values를 import 부재로 실패시킨다.

  • GREEN: 공통 numeric CAN field를 추가하고 Audio inline price markup을 교체하되 paid option reset과 request를 보존한다.

  • RED: Community 최종 preview/media-first order, image 전 audio 부재·image 변경 시 stale audio 제거, 기본 0·blank 거부·항상 price 전송을 실패시킨다.

  • GREEN: Community form state·render order·request를 최소 수정하고 multipart serializer 순서는 변경하지 않는다.

  • REFACTOR: 불필요해진 domain price formatter wrapper를 제거하고 shared/domain focused test, full Gate, 375/768/1280 browser·visual QA와 독립 review를 통과한다.

  • 실행 명령: npm run test:run -- src/shared/ui/__tests__/file-field.test.tsx src/shared/ui/__tests__/can-price-field.test.tsx; npm run test:run -- src/features/audio-contents src/features/community-posts; npm run test:run; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; npm run e2e:mock -- tests/e2e/community.spec.ts; git diff --check.

  • 기대 결과: 모든 명령 exit 0, crop/GIF 적용 전 최종 preview 0건, image 없는 audio input·stale audio multipart 0건, Community valid request의 price 누락 0건, Audio 가격 회귀 0건, object URL 미해제 0건.

  • 수동 확인: mock mode 375px read-only 정책, 768px·1280px Community 생성의 initial/crop/applied/replacement/error 상태를 keyboard·가로 overflow·Korean text 기준으로 확인하고 QA browser/server/temp fixture를 모두 정리한다. 내부 관리자 화면이므로 별도 browser zoom 검증은 제외한다.

P6-R5 진행 기록 (2026-08-04):

  • RED/GREEN: FileField preview·Blob URL·native input 동기화 11개, 공용 CAN field와 Audio/Community 음수·소수·상한·blank·00 zero reset, Community media 순서·조건부 audio·stale state·request 회귀를 실패 확인 후 최소 수정했다.
  • 자동 Gate: npm run test:run — 83 files / 458 tests passed; npm run lint, npm run typecheck, npm run build:dev, npm run build:prod, git diff --check — exit 0. build의 500kB chunk warning은 기존 비차단 경고다.
  • 브라우저: npm run e2e:mock -- tests/e2e/community.spec.ts — Chromium/mobile Chrome 14/14 passed. 375px mobile read-only, 768px·1280px final GIF preview·conditional audio·제거·blank 가격·keyboard·overflow·axe를 확인했다. 사용자 결정에 따라 browser zoom 검증은 제외했다.
  • 리뷰: 목표·품질 review에서 문서 추적성과 Audio raw 00 zero reset blocker를 발견했다. parsePrice(value) === 0으로 보완해 Audio focused 9 files / 69 tests를 통과했고, 문서 상태·파일 지도·Decision Log를 정렬했다.
  • 최종 browser/visual: 768px·1280px Chromium/mobile Chrome에서 PNG crop/applied/replacement/error와 기존 GIF/audio/price 상태를 포함해 Community E2E 14/14를 통과했다. 4개 project/viewport evidence directory의 32개 표준 배율 PNG를 기능·디자인 시스템 및 CJK reviewer가 전수 확인해 PASS/HIGH, blocker 0건으로 판정했다. 은 PRD의 정상 CAN 단위이며, 사용자 결정에 따라 browser zoom 검증은 제외했다.

Task R6.6 — 가격 domain validation submit 경로 복구

Goal 실행 P6-R6: Audio와 Community 가격 입력의 native constraint가 domain submit validation을 우회하지 않게 하고 inline 오류·오류 focus 계약을 복구한다.

  • 연결 리뷰: P6-R5 코드 리뷰REV-P6-R5-001.

  • 시작 조건: P6-R5 완료, REV-P6-R5-001 확정, plan-task.md:3128의 range validation domain submit 소유 계약.

  • 완료 증거: 음수·소수 가격을 실제 submit button으로 제출했을 때 Audio 생성·수정과 Community 생성 모두 domain inline 오류를 표시하고 request를 전송하지 않는 RED/GREEN, focused test·전체 unit·typecheck·lint·build·diff check 통과, 코드-only 재리뷰.

  • Files: Modify: src/features/audio-contents/components/AudioContentForm.tsx, src/features/audio-contents/tests/audio-form.test.tsx, src/features/audio-contents/tests/audio-form-update.test.tsx, src/features/community-posts/components/CommunityPostForm.tsx, src/features/community-posts/tests/community-price-validation.test.tsx, docs/20260725_AI캐릭터관리자웹/plan-task.md; Create: docs/20260725_AI캐릭터관리자웹/reviews/phase6-community-create-media-flow.md.

  • Interfaces: 두 form은 noValidate로 native submit 차단만 해제하고 CanPriceFieldtype=number, min=0, step=1과 domain parseCanPriceInput validation은 유지한다.

  • 범위 밖: 가격 schema·오류 문구·payload 변경, FileField·media flow 변경, 브라우저/E2E 실행.

  • RED: 기존 음수·소수 테스트를 fireEvent.submit(form) 대신 실제 생성/저장 button click으로 실행해 domain inline 오류 assertion이 실패하는 것을 확인한다.

  • GREEN: Audio와 Community form에 noValidate를 추가해 동일 테스트를 통과시킨다.

  • REFACTOR: 추가 abstraction 없이 focused/full 정적 Gate와 staged diff 재리뷰를 통과하고 review·Progress를 누적한다.

  • 실행 명령: npm run test:run -- src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-update.test.tsx src/features/community-posts/tests/community-price-validation.test.tsx; npm run test:run -- src/shared/ui/__tests__/file-field.test.tsx src/shared/ui/__tests__/can-price-field.test.tsx src/features/audio-contents src/features/community-posts; npm run test:run; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; git diff --check.

  • 기대 결과: 모든 명령 exit 0, 음수·소수 실제 submit에서 domain 오류 누락 0건, invalid price request 0건, 기존 FileField/media/price 회귀 0건.

  • 수동 확인: 없음 — 사용자 지시에 따라 browser를 실행하지 않고 실제 submit button을 사용하는 jsdom integration test로 검증한다.

P6-R6 진행 기록 (2026-08-04):

  • RED: Audio 생성·수정과 Community 생성의 invalid price test를 실제 submit button click으로 변경했다. npm run test:run -- src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-update.test.tsx src/features/community-posts/tests/community-price-validation.test.tsx — 음수·소수 inline 오류 누락 6건 실패, 나머지 22건 통과로 native constraint의 submit 차단을 재현했다.
  • GREEN: 두 form에 noValidate만 추가했다. 동일 명령 — 3 files / 28 tests passed.
  • REFACTOR/Gate: 추가 abstraction 없음. domain focused — 18 files / 134 tests passed; full unit — 83 files / 458 tests passed; npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, git diff --check — exit 0. build의 500kB chunk warning은 기존 비차단 경고다.
  • 리뷰: P6-R5 코드 리뷰REV-P6-R5-001을 수정 완료로 판정했다. 독립 코드 재리뷰에서 form-level noValidate가 기존 preview 시간 pattern 검증도 해제하는 REV-P6-R5-002를 추가 확정해 P6-R7로 전환했다. 사용자 지시에 따라 browser/E2E는 실행하지 않았다.

Task R6.7 — Audio preview 시간 domain validation 복구

Goal 실행 P6-R7: Audio form의 noValidate를 유지하면서 preview 시작·종료의 완전한 HH:MM:SS 형식을 domain submit validation으로 보장한다.

  • 연결 리뷰: P6-R5 코드 리뷰REV-P6-R5-002.

  • 시작 조건: P6-R6 완료, REV-P6-R5-002 확정, PRD AUDIO-033.

  • 완료 증거: preview 생성이 켜진 Audio 생성에서 잘못된 시작·종료 값을 실제 submit button으로 제출하면 inline 오류를 표시하고 request를 전송하지 않는 RED/GREEN, Audio focused·전체 unit·typecheck·lint·build·diff check 통과, 코드-only 재리뷰.

  • Files: Modify: src/features/audio-contents/components/AudioContentForm.tsx, src/features/audio-contents/components/AudioContentCreateOptions.tsx, src/features/audio-contents/tests/audio-form-create-red.test.tsx, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase6-community-create-media-flow.md.

  • Interfaces: noValidate와 가격 domain validation은 유지한다. preview 생성이 켜지고 값이 입력된 경우에만 기존 input pattern과 동일한 완전한 HH:MM:SS 형식을 domain에서 검증해 inline 오류와 오류 focus를 제공한다.

  • 범위 밖: preview offset의 오디오 duration 상관관계, nullable contract 변경, 브라우저/E2E 실행.

  • RED: 잘못된 preview 시작·종료 값을 실제 생성 button으로 제출해 request가 전송되는 실패를 확인한다.

  • GREEN: Audio domain validation과 input 오류 연결을 최소 추가해 request를 차단한다.

  • REFACTOR: 추가 abstraction 없이 Audio focused/full 정적 Gate와 코드 재리뷰를 통과하고 review·Progress를 누적한다.

  • 실행 명령: npm run test:run -- src/features/audio-contents/tests/audio-form-create-red.test.tsx; npm run test:run -- src/features/audio-contents; npm run test:run; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; git diff --check.

  • 기대 결과: 모든 명령 exit 0, malformed preview time request 0건, 정상 HH:MM:SS·가격 domain validation 회귀 0건.

  • 수동 확인: 없음 — 사용자 지시에 따라 browser를 실행하지 않고 실제 submit button을 사용하는 jsdom integration test로 검증한다.

P6-R7 진행 기록 (2026-08-04):

  • RED: malformed preview 시작·종료를 실제 생성 button으로 제출하는 test를 추가했다. npm run test:run -- src/features/audio-contents/tests/audio-form-create-red.test.tsx — 신규 1건 실패, 기존 9건 통과로 inline 오류 없이 request 경로가 진행되는 회귀를 재현했다.
  • GREEN: preview 생성이 켜지고 값이 입력된 경우에만 기존 input pattern과 같은 HH:MM:SS 형식을 domain에서 검사하고, 시작·종료 inline 오류·aria-describedby·aria-invalid·첫 오류 focus를 연결했다. 동일 명령 — 1 file / 10 tests passed.
  • REFACTOR/Gate: 추가 abstraction 없음. Audio·Community·shared 회귀 — 18 files / 135 tests passed; full unit — 83 files / 459 tests passed; npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — exit 0. build의 500kB chunk warning은 기존 비차단 경고다.
  • 리뷰: 두 noValidate form의 native constraint와 domain 검증을 다시 추적했다. Audio 예약 일시는 기존 domain 검증이 담당하며 Community/FileField/media/price를 포함한 추가 확정 발견 사항은 없었다. 사용자 지시에 따라 browser/E2E는 실행하지 않았다.

Phase 7. FanTalk vertical slice

목표: 모든 viewport에서 FanTalk 목록을 조회하고 답변이 없는 item에 한 번 답변한다. 상세·답변 수정·전체 결과 filter/sort는 계약 제공 후 후속 slice로 추가한다.

Phase Goal P7: Task 7.1 → 7.3과 Phase 7 Gate로 OpenAPI가 제공한 FanTalk 목록·단일 답변 생성 slice를 완성한다.

  • 시작 조건: P3-T2 workspace core와 P2-GATE 완료.
  • 완료 조건: 제공된 목록 GET·답변 POST 범위의 P7-T1~P7-T3, P7-GATE 완료. 미제공 상세·수정·filter/sort·유일성 오류는 외부 의존 상태와 후속 재개 조건 기록.
  • 실행 순서: 계약 확인 → 목록/답변 → 반응형·접근성.

요구사항: FANTALK-001~011, PRD 9의 FanTalk 범위.

외부 의존: EXT-004 별도 상세·답변 수정 endpoint/DTO, 전체 결과 답변 상태 filter, sort, reply uniqueness의 원자적 강제와 중복 오류 계약. 목록 GET과 답변 POST는 제공됐다.

주요 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.tsx
  • Create: src/features/fan-talks/components/{FanTalkList,FanTalkListItem,FanTalkReplySheet,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

Phase 7 Task 실행 계약

Goal Files Interfaces TDD·검증 기준
P7-T1 Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md; Read: docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json; Test: 없음 Consumes: FanTalkList*, FanTalkReply* schema·2 operation. Produces: 제공 목록/reply map, 미제공 범위와 Sheet inventory TDD 예외: 외부 계약 조사 Task다. OpenAPI JSON parse와 `rg -n 'FANTALK-0(0[7-9]
P7-T2 Create: src/features/fan-talks/api/fan-talk-api.ts, src/features/fan-talks/model/types.ts, src/features/fan-talks/schemas/fan-talk-reply-schema.ts, src/features/fan-talks/pages/FanTalkListPage.tsx, src/features/fan-talks/components/{FanTalkList,FanTalkListItem,FanTalkReplySheet,FanTalkReplyForm}.tsx, src/features/fan-talks/tests/{fan-talk-contract.test.ts,fan-talk-list.test.tsx,fan-talk-reply.test.tsx}; Modify: src/app/router.tsx Consumes: FanTalkListResponse, FanTalkReplyCreateRequest, FanTalkReplyResponse. Produces: getFanTalks({characterId,page,size}), createFanTalkReply, list-item Sheet flow TDD 적용: npm run test:run -- src/features/fan-talks/tests/fan-talk-contract.test.ts src/features/fan-talks/tests/fan-talk-list.test.tsx src/features/fan-talks/tests/fan-talk-reply.test.tsx; 기대 exit 0. 수동 확인: detail/filter/edit request 0회와 한 번 reply.
P7-T3 Modify: src/features/fan-talks/pages/FanTalkListPage.tsx, src/features/fan-talks/components/{FanTalkList,FanTalkListItem,FanTalkReplySheet,FanTalkReplyForm}.tsx; Test: tests/e2e/fan-talk.spec.ts Consumes: P7-T2 UI. Produces: 전 viewport reply capability evidence TDD 적용: npm run e2e:mock -- tests/e2e/fan-talk.spec.ts; 기대 지원 project 전부 통과. 수동 확인: 320px keyboard, 200% zoom, keyboard-only, axe.

P7-T2~P7-T3는 각 row의 focused test로 RED → GREEN → REFACTOR를 실행하고 관련 feature test·typecheck·lint 결과와 수동 확인을 §7 검증 기록에 누적한다.

Task 7.1 Phase 계약 확인

Goal 실행 P7-T1: FanTalk 목록·답변 생성 계약과 미제공 상세·수정·filter/sort·유일성 범위, mock 가능 범위와 component map을 확정한다.

  • 시작 조건: P3-T2, P2-GATE 완료, PRD FANTALK-001~011, MOCK-001~009와 OpenAPI FanTalk 2개 operation/schema 확인.

  • 완료 증거: 체크박스 전체, 제공 범위와 외부 의존의 PRD·OpenAPI·plan 일치.

  • 범위 밖: 임시 endpoint·placeholder DTO·production mock adapter.

  • 목록 page/size, data.fanTalkCount/fanTalks/page/size/hasNext, item creatorReplies와 답변 POST {content}·성공 DTO를 contract fixture로 고정한다.

  • 별도 상세·답변 수정 endpoint, answer filter와 sort query가 없음을 기록하고 임시 route·query를 만들지 않는다.

  • 답변 1개를 server가 원자적으로 강제하는 방식과 중복 생성 비2xx status/message key를 기록한다.

  • 미제공 범위에는 임시 endpoint·DTO·production adapter를 만들지 않고 후속 재개 조건을 문서화한다.

  • 목록·reply Sheet/form의 상태/action inventory를 작성하고 Page는 route/query/policy 조합, feature component는 FanTalk 규칙, shared component는 Phase 1 contract를 사용하도록 component map을 확정한다.

  • 제공된 목록·답변 생성 범위만 browser fixture로 만들고 상세·수정·filter/sort·유일성 오류는 mock에서도 추정하지 않는다.

Task 7.2 목록·답변 생성

Goal 실행 P7-T2: backend 순서를 유지하는 목록과 답변 1회 생성 흐름을 완성한다.

  • 시작 조건: P7-T1 완료.

  • 완료 증거: 체크박스 전체, list/reply contract·UI test, 중복 제출 차단·오류 복구, OQ-009 후속 검토 기록.

  • 범위 밖: 별도 상세, 답변 수정·삭제·두 번째 답변, 전체 결과 filter/sort와 계약 없는 network 동작.

  • 목록은 page, size를 URL에 보존하고 backend 반환 순서와 hasNext를 사용한다. 전체/미답변/답변 완료 filter와 client 재정렬은 만들지 않는다.

  • loading·empty·error·retry와 목록 새로고침을 test하고 /fan-talks/:fanTalkId route·상세 GET이 0건임을 검증한다.

  • 목록 item의 creatorReplies가 비어 있을 때만 POST form을 표시하고 답변이 있으면 읽기 전용으로 표시하며 edit/delete UI는 만들지 않는다.

  • 빠른 두 번 제출에도 POST가 한 번만 호출되는 test를 작성한다.

  • 답변 POST 성공 DTO의 fanTalkId, replyId, creatorMemberId, content, createdAtUtc를 반영하고 해당 목록 page를 재조회한다.

  • server 중복 오류를 받으면 현재 목록 page를 재조회해 creatorReplies를 갱신하고 status/key를 추정 분기하지 않는다.

  • 저장 중 중복 제출 차단, visible label, 오류 연결, 성공 live feedback을 test한다.

  • 초기 FanTalk reply form을 실제 페이지에서 확인한 뒤 content 최대 길이 권고안을 작성한다. backend 호환 확인 전에는 상한을 구현하지 않는다.

  • 제공 계약 범위의 mock store로 미답변 → 답변 생성 → 읽기 전용 답변 표시의 최종 UI E2E를 작성한다.

Task 7.3 FanTalk 반응형·접근성

Goal 실행 P7-T3: 모든 viewport의 FanTalk 응대 capability와 접근성을 검증한다.

  • 시작 조건: P7-T2 완료.

  • 완료 증거: 체크박스 전체, 320px keyboard viewport·keyboard-only·200% zoom·axe E2E 기록.

  • 범위 밖: viewport별 기능 축소와 답변 삭제.

  • desktop/tablet/mobile 모두 목록 조회·답변 작성을 제공하고 수정 action은 제공하지 않는다.

  • 320px에서 keyboard가 reply input/submit을 가리지 않는 E2E를 작성한다.

  • keyboard-only 목록 탐색·reply Sheet·create, 200% zoom, axe critical·serious 0건을 확인한다.

Phase 7 Gate

Goal 실행 P7-GATE: FanTalk mock UI journey와 실제 server integration 상태를 분리해 판정한다.

  • 시작 조건: P7-T1~P7-T3 활성 범위 완료.
  • 완료 증거: 아래 명령과 Expected 통과 또는 Phase 제외 결정의 검증 기록.
  • 범위 밖: 계약 미제공 상태에서 network integration 완료 주장.
npm run test:run -- src/features/fan-talks
npm run e2e:mock -- tests/e2e/fan-talk.spec.ts
npm run e2e -- tests/e2e/fan-talk.spec.ts
npm run typecheck
npm run lint
npm run build

Expected: mock mode에서 FanTalk page 조회 → 목록 item Sheet → 답변 1회 생성 → 읽기 전용 답변 표시 UI가 모든 viewport에서 통과하며 상세 GET, filter/sort query, 두 번째 reply, edit/delete UI가 없다. 미제공 기능은 외부 의존으로 남고 목록·답변 생성 범위와 섞여 완료 표시되지 않는다.

수동 확인: desktop/tablet/mobile에서 목록·reply Sheet를 keyboard-only로 확인하고, creatorReplies가 있는 item의 POST가 차단되며 상세·수정·filter request가 발생하지 않는지 본다.

Task R7.1 — FanTalk 날짜와 저장 성공 문구 현지화

Goal 실행 P7-R1: FanTalk 목록·Sheet·답변의 UTC 시각을 공통 서울 시각 formatter로 표시하고 저장 성공 안내에서 내부 ID를 제거한다.

  • 연결 리뷰: Phase 7 현재 리뷰REV-P7-001

  • 시작 조건: FANTALK-009~010, PRD §13 중앙 날짜 formatter와 한국어 운영 UI.

  • 완료 증거: raw UTC·내부 ID 노출 실패 test, 최소 표시 수정, FanTalk focused unit·mock E2E·Phase Gate와 검증 기록.

  • Files: Modify: FanTalkList.tsx, FanTalkListItem.tsx, FanTalkReplySheet.tsx, fan-talk-list.test.tsx, fan-talk-reply.test.tsx, tests/e2e/fan-talk.spec.ts.

  • Interfaces: createdAtUtcformatSeoulDateTime으로 표시하고, 성공 status는 등록/수정 결과를 한국어로 안내한다. response ID는 DTO parse와 refresh에만 사용한다.

  • 범위 밖: API 응답 field 제거, locale 선택 UI, FanTalk 정렬.

  • RED: raw 2026-...Z, fanTalk/reply/creator ID 문자열이 사용자 화면에 노출되는 실패 test를 작성한다.

  • GREEN: 공통 formatter와 등록/수정 성공 copy를 목록·카드·Sheet에 적용한다.

  • REFACTOR: 생성/수정 success 상태를 같은 사용자 중심 용어로 정리하고 DTO assertion은 contract test에 유지한다.

  • 실행 명령: npm run test:run -- src/features/fan-talks; npm run e2e:mock -- tests/e2e/fan-talk.spec.ts; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, FanTalk 화면의 raw UTC·내부 ID 노출 0건, reply ID mapping 회귀 0건.

  • 수동 확인: desktop/mobile에서 원글·답변 날짜와 등록/수정 성공 알림을 확인한다.


Phase 8. Comments vertical slice

목표: Audio detail과 Community Sheet 양쪽에서 같은 2단계 댓글 UX를 제공하고 작성자별 수정·soft delete 권한을 일관되게 적용한다.

Phase Goal P8: Task 8.1 → 8.4와 Phase 8 Gate로 두 target의 2단계 Comments slice를 완성한다.

  • 시작 조건: P4-T2 Audio detail, P6-T2 Community Sheet와 P2-GATE 완료.
  • 완료 조건: 핵심 계약이 제공되면 P8-T1~P8-T4, P8-GATE 완료. 미제공이면 Phase 제외/후속 결정 문서화로 종결.
  • 실행 순서: 계약 확인 → target/thread → 권한 action → 반응형·접근성.

요구사항: COMMENT-001~006, PRD 9의 Comments 범위.

외부 의존: EXT-005는 2026-07-29 OpenAPI 2.3.0에서 해결됐고, 구현은 Phase 10 P10-T6에서 완료한다. 2026-07-28 당시 OpenAPI의 Audio 상세 commentList와 Community 목록 firstComment는 읽기용 요약일 뿐 CRUD 계약이 아니었으므로 과거 제외 기록은 §7에 보존한다.

주요 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

Phase 8 Task 실행 계약

Goal Files Interfaces TDD·검증 기준
P8-T1 Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md; Read: docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json; Test: 없음 Consumes: Audio commentList, Community firstComment summary와 CRUD operation 부재. Produces: target별 외부 의존·재개 조건과 screen inventory TDD 예외: 계약 부재 조사 Task다. OpenAPI JSON parse와 `rg -n 'COMMENT-00[1-6]
P8-T2 계약 제공 후 Create: src/features/comments/api/comment-api.ts, src/features/comments/model/{types,comment-target}.ts, src/features/comments/schemas/comment-schema.ts, src/features/comments/components/{CommentThread,CommentForm,CommunityPostCommentsSheet}.tsx, src/features/comments/tests/{comment-contract.test.ts,comment-thread.test.tsx}; Modify: src/features/audio-contents/pages/AudioContentDetailPage.tsx, src/features/community-posts/components/CommunityPostSheet.tsx Consumes: backend가 제공할 target별 list/create/update/delete DTO. Produces: CommentTarget, target adapter와 2단계 thread TDD 적용: 계약 제공 후 npm run test:run -- src/features/comments/tests/comment-contract.test.ts src/features/comments/tests/comment-thread.test.tsx; 기대 exit 0. 수동 확인: 두 target과 root/direct reply만 표시.
P8-T3 계약 제공 후 Create: src/features/comments/components/CommentActions.tsx, src/features/comments/tests/comment-permissions.test.tsx; Modify: src/features/comments/api/comment-api.ts, src/features/comments/components/CommentThread.tsx Consumes: P8-T2 adapter와 ownership/error contract. Produces: create/update/soft-delete action policy TDD 적용: 계약 제공 후 npm run test:run -- src/features/comments/tests/comment-contract.test.ts src/features/comments/tests/comment-permissions.test.tsx; 기대 exit 0. 수동 확인: author별 action과 fan edit 0회.
P8-T4 계약 제공 후 Modify: src/features/comments/components/{CommentThread,CommentForm,CommentActions,CommunityPostCommentsSheet}.tsx, src/features/audio-contents/pages/AudioContentDetailPage.tsx, src/features/community-posts/components/CommunityPostSheet.tsx; Test: tests/e2e/comments.spec.ts Consumes: P8-T2/T3 UI. Produces: 전 viewport Comments capability evidence TDD 적용: 계약 제공 후 npm run e2e:mock -- tests/e2e/comments.spec.ts; 기대 지원 project 전부 통과. 수동 확인: 320px, keyboard, focus 복귀, 200% zoom, axe.

P8-T2~P8-T4는 CRUD 계약이 제공된 뒤 각 row의 focused test로 RED → GREEN → REFACTOR를 실행한다. 계약 전에는 test용 endpoint·DTO를 만들지 않고 P8-T1의 대체 검증 결과만 §7 검증 기록에 누적한다.

Task 8.1 Phase 계약 확인

Goal 실행 P8-T1: 두 댓글 target의 CRUD·2단계·권한 오류 계약, mock 가능 범위와 component map을 확정한다.

  • 시작 조건: P4-T2, P6-T2, P2-GATE 완료, PRD COMMENT-001~006, MOCK-001~009와 OpenAPI의 comment summary schema 및 CRUD operation 부재 확인.

  • 완료 증거: 체크박스 전체, 제공 계약 또는 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을 확정한다.

  • target별 CRUD·2단계·권한 오류 계약이 제공된 뒤에만 browser fixture를 만들고 현재 미제공 계약을 mock DTO로 추정하지 않는다.

Task 8.2 target adapter·2단계 thread

Goal 실행 P8-T2: Audio/Community target adapter와 정확히 2단계인 comment thread 조회를 완성한다.

  • 시작 조건: P8-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 8.3 작성자별 action

Goal 실행 P8-T3: AI Character와 fan 작성자별 수정·soft delete 권한을 일관되게 적용한다.

  • 시작 조건: P8-T1, P8-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한다.

  • 댓글 CRUD 계약 제공 후 초기 Comment thread/form을 실제 페이지에서 확인하고 content 최대 길이 권고안을 작성한다. backend 호환 확인 전에는 상한을 구현하지 않는다.

  • 계약 제공 후 mock store로 양 target의 root/reply CRUD와 작성자별 권한 오류 최종 UI E2E를 작성한다.

Task 8.4 Comments 반응형·접근성

Goal 실행 P8-T4: 모든 viewport의 Comments CRUD capability와 thread/form/dialog 접근성을 검증한다.

  • 시작 조건: P8-T2, P8-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 8 Gate

Goal 실행 P8-GATE: Comments mock UI journey와 실제 server integration 상태를 분리해 판정한다.

  • 시작 조건: P8-T1~P8-T4 활성 범위 완료.
  • 완료 증거: 아래 명령과 Expected 통과 또는 Phase 제외 결정의 검증 기록.
  • 범위 밖: 계약 미제공 상태에서 network integration 완료 주장.
npm run test:run -- src/features/comments
npm run e2e:mock -- tests/e2e/comments.spec.ts
npm run e2e -- tests/e2e/comments.spec.ts
npm run typecheck
npm run lint
npm run build

Expected: 핵심 계약 제공 후 mock mode에서 Audio와 Community 두 진입점의 2단계 댓글 CRUD·권한·모바일 최종 UI가 통과하고 reply의 reply 및 fan edit request는 생성되지 않는다. 계약 미제공이면 mock fixture도 만들지 않고 연동 대기로 기록한다.

수동 확인: 댓글 CRUD 계약 제공 후 두 target에서 root/direct reply, 작성자별 action, 320px keyboard와 focus 복귀를 확인한다. 계약 미제공이면 Comments network request와 browser fixture가 0건인지 확인한다.

Task R8.1 — Comment reply pagination과 empty state 완성

Goal 실행 P8-R1: 루트별 모든 direct reply page를 관리하고 댓글 0건을 명시적 empty state로 표시한다.

  • 연결 리뷰: Phase 8 재리뷰REV-P8-001, REV-P8-002

  • 시작 조건: COMMENT-001, COMMENT-006~007, reply response totalCount/items.

  • Files: Modify: src/features/comments/components/CommentThread.tsx, src/features/comments/tests/comment-thread.test.tsx, src/features/comments/tests/comment-contract.test.ts, tests/e2e/comments.spec.ts, 필요 시 comment mock fixtures/store.

  • Interfaces: root별 reply state에 page,size=20,data; mutation 뒤 현재 page refetch, 빈 root 목록에서도 create form 유지.

  • 범위 밖: 3단계 reply, reply page size 선택, infinite scroll.

  • RED: 21개 reply에서 page 1 요청·표시, 마지막 reply 삭제 후 유효 page 복귀, root 0건 empty state test를 작성한다.

  • GREEN: root별 page state와 ResourcePagination, root empty PageState를 추가한다.

  • REFACTOR: reply loading/error/content state를 root page와 함께 정리하고 Audio/Community 소비 회귀를 실행한다.

  • 실행 명령: npm run test:run -- src/features/comments src/features/audio-contents src/features/community-posts; npm run e2e:mock -- tests/e2e/comments.spec.ts.

  • 기대 결과: exit 0, 21번째 이후 reply 접근 가능, reply-of-reply UI 0건, empty state와 create form 동시 노출.

  • 수동 확인: 320px/desktop에서 reply 다음·이전 page, mutation 후 현재 page와 keyboard focus를 확인한다.

Task R8.2 — Comments mock mutation 불변식 복구

Goal 실행 P8-R2: Comments mock store가 root/direct reply 2단계, AI 작성 row만 수정, 단일 row soft delete 계약을 그대로 재현하게 한다.

  • 연결 리뷰: Phase 8 현재 리뷰REV-P8-003

  • 시작 조건: COMMENT-002~004, COMMENT-007, MOCK-005, Audio·Community comment OpenAPI PUT/DELETE.

  • 완료 증거: mock handler 실패 재현 test, 최소 store 수정, Comments contract/unit·mock E2E·Phase Gate와 검증 기록.

  • Files: Modify: src/shared/mocks/comment-mock-store.ts, src/features/comments/tests/comment-contract.test.ts, 필요 시 tests/e2e/comments.spec.ts.

  • Interfaces: parentId는 같은 target의 활성 root만 허용하고, PUT은 writerId === target creatorId인 row만 허용하며, DELETE는 대상 row만 비활성화/제외하고 자식 row를 변경하지 않는다.

  • 범위 밖: 실제 backend 구현, 3단계 thread UI, 팬 댓글 수정 권한 확대.

  • RED: reply를 parent로 한 POST와 팬 row PUT은 비2xx, root DELETE 뒤 자식 row 보존을 검증하는 Audio·Community mock contract test를 작성한다.

  • GREEN: store validation과 delete mutation을 계약에 맞게 수정한다.

  • REFACTOR: target/ownership/root 검증을 작은 predicate로 정리하고 UI의 팬 PUT 0건 test를 유지한다.

  • 실행 명령: npm run test:run -- src/features/comments src/shared/mocks; npm run e2e:mock -- tests/e2e/comments.spec.ts; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 3단계 POST·팬 PUT 성공 0건, root DELETE 후 하위 reply 변경 0건.

  • 수동 확인: Audio와 Community mock에서 팬/AI row action과 root 삭제 후 목록 재조회 동작을 확인한다.

Progress — 2026-07-30 P8-R2 완료

  • RED: npm run test:run -- src/features/comments/tests/comment-contract.test.ts는 신규 Audio·Community 3단계 POST, fan PUT, root DELETE 뒤 답글 보존 assertion에서 1 file / 3 failed / 3 passed로 실패했다.
  • GREEN: CommentMockStore에 target root parent와 creator-owned predicate를 추가하고, DELETE filter를 대상 ID만 제외하도록 축소했다. 같은 focused contract test는 1 file / 6 tests passed였다.
  • REFACTOR: API schema/path/UI는 변경하지 않았다. 기존 Chromium E2E의 fan PUT 0-request assertion을 유지했다.

Task R8.3 — Comments 공통 pagination size 계약 복구

Goal 실행 P8-R3: Audio·Community Comments adapter가 공통 size >= 1을 그대로 사용하고 FanTalk 전용 20..50 clamp를 적용하지 않게 한다.

  • 연결 리뷰: Phase 8 현재 리뷰REV-P8-004

  • 시작 조건: PRD §11.1 공통 pagination, OpenAPI 공통 Size parameter의 default 20, minimum 1, maximum 없음.

  • 완료 증거: size=1, size=51 request path 실패 test, adapter 최소 수정, Audio·Community Comments unit/mock E2E 통과.

  • Files: Modify: src/features/comments/api/comment-api.ts, src/features/comments/tests/comment-contract.test.ts; 필요 시 Comments API adapter focused test.

  • Interfaces: page는 기본 0·최소 0, size는 기본 20·최소 1; 20..50 보정은 FanTalk에만 남긴다.

  • 범위 밖: Comments UI page-size option 변경, server pagination metadata 변경, FanTalk pagination 변경.

  • RED: root/replies GET에서 size=1size=51이 각각 그대로 query에 남아야 한다는 contract test를 작성했다.

  • GREEN: Comments normalizeSize를 기본 20·최소 1만 적용하도록 수정했다.

  • REFACTOR: FanTalk 전용 규칙을 공유 helper로 끌어올리지 않고 Comments path 생성 중복만 현재 수준에서 유지했다.

  • 실행 명령: npm run test:run -- src/features/comments; npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium; npm run typecheck; npm run lint.

  • 기대 결과: 모든 명령 exit 0, size=1·size=51 보존, size<=0만 1로 보정, 기존 댓글 흐름 회귀 0건.

  • 수동 확인: 기본 size=20 화면과 reply pagination이 기존처럼 동작하는지 확인한다.

P8-R3 수정 검증 기록 (2026-07-30):

  • RED: npm run test:run -- src/features/comments/tests/comment-contract.test.ts — 1 failed / 5 passed. root size=1size=20으로, replies size=51size=50으로 바뀌어 기대대로 실패했다.
  • GREEN focused: 같은 command — 1 file / 6 tests passed. root/replies query가 size=1size=51을 보존함을 확인했다.
  • REFACTOR/회귀: npm run test:run -- src/features/comments — 2 files / 11 tests passed. npm run typecheck, npm run lint — 모두 exit 0.
  • LSP diagnostics: src/features/comments/api/comment-api.ts, src/features/comments/tests/comment-contract.test.ts — 오류 0건.
  • E2E/수동 확인: 사용자 지시에 따라 개발 중 E2E는 실행하지 않고 P10-R5 이후 최종 E2E에서 확인한다.

Task R8.4 — 댓글 전송 실패 시 초안 보존과 재시도 복구

Goal 실행 P8-R4: 루트 댓글·답글 POST가 실패하면 입력 초안을 유지하고 같은 내용으로 재시도한 뒤 성공할 때만 입력을 비운다.

  • 연결 리뷰: Phase 8 현재 리뷰REV-P8-005

  • 시작 조건: PRD §10.5 오류·재시도 상태, 현재 CommentThread.runMutation이 오류를 내부 처리하고 성공처럼 resolve해 CommentForm이 실패 후에도 입력을 비우는 구조.

  • 완료 증거: Audio·Community 루트/답글 POST reject 후 textarea 값 유지와 재시도 성공 실패 test, submit 성공 여부 contract 최소 수정, Comments focused unit·mock E2E·정적 Gate 통과.

  • Files: Modify: src/features/comments/components/CommentForm.tsx, src/features/comments/components/CommentThread.tsx, src/features/comments/tests/comment-thread.test.tsx; Test: tests/e2e/comments.spec.ts.

  • Interfaces: CommentForm.onSubmit은 mutation 성공 여부를 반환하고 입력은 성공일 때만 초기화한다. 기존 API payload, root/reply cache 재조회, 전역 pending guard는 유지한다.

  • 범위 밖: 댓글 localStorage draft, offline queue, optimistic update, edit form 동작 변경.

  • Phase 8 Gate 연결: Audio·Community 루트/답글 실패·재시도 focused test와 Comments mock E2E, typecheck·lint·build가 모두 통과하기 전에는 종결하지 않는다.

  • RED: 루트 댓글과 열린 답글 form의 첫 POST를 reject해 visible server error와 기존 textarea 값 유지, 두 번째 POST 성공 후 값 초기화를 기대하는 실패 test를 작성했다.

  • GREEN: runMutation과 submit callback이 성공 여부를 반환하게 하고 CommentForm은 성공한 경우에만 초안과 local error를 초기화했다.

  • REFACTOR: 새 form/state library 없이 현재 component contract만 최소 변경하고 update/delete 및 pending 재진입 회귀를 확인했다.

  • 실행 명령: npm run test:run -- src/features/comments; npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, POST 실패 후 루트/답글 초안 유지, 같은 form 재시도 성공, 성공 후에만 textarea 초기화, 중복 request 0건.

  • 수동 확인: mock 500 응답에서 루트 댓글과 답글을 각각 전송해 오류·초안 유지·재시도를 확인한다.

P8-R4 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/features/comments/tests/comment-thread.test.tsx — 1 failed / 5 passed. 루트 댓글 POST 실패 후 CommentForm textarea가 빈 값으로 바뀌어 기대대로 실패했다.
  • GREEN: CommentForm.onSubmit이 성공 여부를 반환하게 하고, CommentThread.runMutation이 성공 시 true, 실패·pending 재진입 차단 시 false를 반환하게 했다. 입력값과 local error는 성공한 경우에만 초기화된다.
  • Focused: npm run test:run -- src/features/comments/tests/comment-thread.test.tsx — 1 file / 6 tests passed. 루트와 답글 모두 첫 실패 후 초안 유지, 같은 form 재시도 성공 후 값 초기화를 확인했다.
  • REFACTOR/회귀: npm run test:run -- src/features/comments — 3 files / 13 tests passed. src/features/comments LSP diagnostics — 5 TSX files / 0 diagnostics. 새 form/state library나 optimistic/localStorage draft는 추가하지 않았다.
  • E2E/수동 확인: 사용자 지시에 따라 개발 중 반복 E2E는 실행하지 않고 최종 회귀 단계에서 Comments mock E2E를 필요 시 실행한다.

Phase 9. 교차 회귀·인수인계

목표: 새 기능을 추가하지 않고 활성 릴리스 범위 전체가 PRD, API Contract, 보안, 반응형, 접근성 기준을 만족한다는 최신 증거를 남긴다.

Phase Goal P9: Task 9.1 → 9.3과 Phase 9 Gate로 활성 릴리스 범위의 최종 인수 증거를 완성한다.

  • 시작 조건: 활성 범위의 P0-GATE~P8-GATE 완료 또는 명시적 제외 결정.
  • 완료 조건: P9-T1~P9-T3, P9-GATE 완료, 활성 mock UI와 server allowlist 자동 Gate 0 failure/0 error, 수동 검증 기록. 도메인별 server integration은 별도 backend 계약·server-mode allowlist가 충족될 때 완료한다.
  • 실행 순서: 교차 journey/오류 → 반응형/접근성/보안 → 문서/품질 → 전체 Gate.

주요 Files:

  • Create: tests/e2e/{resource-workflows,error-mapping,responsive-capabilities,accessibility}.spec.ts
  • Modify: README.md
  • Modify: docs/20260725_AI캐릭터관리자웹/{prd.md,plan-task.md} when an actual product or implementation decision differs.
  • Replace from backend: docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json only when a new formal contract version is provided.

Phase 9 Task 실행 계약

Goal Files Interfaces TDD·검증 기준
P9-T1 Create: tests/e2e/resource-workflows.spec.ts, tests/e2e/error-mapping.spec.ts Consumes: 활성 Phase API/UI와 OpenAPI common responses. Produces: 교차 journey·serializer·error regression evidence TDD 적용: 기존 회귀가 놓치는 불변식의 실패 E2E를 먼저 추가하고 npm run e2e:mock -- tests/e2e/resource-workflows.spec.ts tests/e2e/error-mapping.spec.ts; 기대 exit 0. 수동 확인: 406 포함 오류, 금지 request 0회.
P9-T2 Create: tests/e2e/responsive-capabilities.spec.ts, tests/e2e/accessibility.spec.ts Consumes: PRD viewport matrix와 P9-T1 journey. Produces: responsive·a11y·security evidence와 별도 회귀 수정 Task 입력 TDD 적용: 실패 viewport/a11y 회귀를 먼저 재현하고 npm run e2e:mock -- tests/e2e/responsive-capabilities.spec.ts tests/e2e/accessibility.spec.ts; 기대 exit 0. 수동 확인: browser matrix, keyboard, zoom, 민감정보 비기록.
P9-T3 Modify: README.md, docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md; Test: 없음 Consumes: P9-T1/T2 실제 결과와 외부 의존 상태. Produces: 요구사항 추적·Progress·인수 문서 TDD 예외: 문서 정합성 Task다. rg 추적 검사, Markdown link 확인과 git diff --check를 실행한다. 기대 exit 0. 수동 확인: 모든 확정·외부 의존·OQ-009 후속 값이 증거와 연결된다.

P9-T1~P9-T2는 row의 E2E로 RED → GREEN → REFACTOR를 실행하고 server mode 회귀·typecheck·lint·build를 이어서 수행한다. P9-T3는 실행한 대체 검증과 수동 대조 결과를 §7 검증 기록에 누적한다.

Task 9.1 교차 journey·오류 회귀

Goal 실행 P9-T1: 활성 도메인의 mock/server 교차 journey, 오류 매핑과 serializer/security 불변식을 회귀 검증한다.

  • 시작 조건: 활성 범위의 Phase 0~8 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 list/reply → Comments 중 계약이 제공된 활성 범위 journey를 검증한다.

  • 400/401/403/404/405/406/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에서 다시 검증한다.

  • 모든 활성 도메인의 mock handler가 정규화 endpoint·DTO를 사용하고 실제 backend request 0건으로 최종 UI journey를 재현하는지 검증한다.

  • server mode의 404·network error가 mock으로 전환되지 않고 실제 오류 UI와 연동 대기 기록으로 남는지 검증한다.

Task 9.2 반응형·접근성·보안 회귀

Goal 실행 P9-T2: 지원 viewport/browser의 capability, 접근성과 민감정보 비기록을 mock/server 양 mode에서 검증한다.

  • 시작 조건: P9-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을 확인한다.

  • 로컬 자동 Gate의 Chromium/mobile Chrome matrix를 확인한다. WebKit·Mobile Safari와 실제 Edge/Safari 기기 QA는 2026-07-31 제품 결정으로 지원 범위에서 제외한다.

  • 모든 핵심 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 검증 검색을 다시 실행하고 채택·제외 결과를 기록한다.

  • mock mode banner가 모든 viewport에서 핵심 UI를 가리지 않고 실제 server와 혼동되지 않는지 확인한다.

Task 9.3 문서·품질 Gate

Goal 실행 P9-T3: 요구사항 추적, 외부 의존·Open Question, mock/server 상태, README와 검증 기록을 실제 구현에 맞춘다.

  • 시작 조건: P9-T1, P9-T2 완료.

  • 완료 증거: 체크박스 전체, PRD 수용 기준별 증거 연결, 독립 code review 수정과 관련 Gate 재실행 기록.

  • 범위 밖: 결정되지 않은 계약을 문서상 확정하는 행위.

  • 활성 범위의 P0 외부 의존이 0건인지, 아니면 구현 전에 명시적으로 후속/제외 결정됐는지 확인한다.

  • OQ-009의 확정 절차에 따라 각 초기 UI의 최대 길이·배열 개수 권고안, backend 호환 결과와 실제 값 또는 “상한 추가 없음”을 기록하고 중복 checklist를 남기지 않는다.

  • OQ-010 감사 로그 UI가 현재 릴리스 non-goal임을 결정 기록과 맞춘다.

  • 실제 구현과 다른 제품 결정은 PRD 결정 기록 → plan 순으로 갱신한다. API 사실이 달라졌다면 backend가 제공한 새 OpenAPI 계약을 먼저 반영한 뒤 두 문서를 맞춘다.

  • PRD 수용 기준마다 자동 test 또는 수동 검증 증거를 연결한다.

  • README에 install, env, run, test, build, 지원 브라우저, 알려진 backend 제약을 기록한다.

  • plan 하단 검증 기록에 무엇을/왜/어떻게와 실제 명령·성공/실패/불가 사유를 누적한다.

  • 별도 code review를 받고 지적사항 수정 후 관련 문서 검증을 다시 실행한다. 전체 Gate는 P9-GATE에서 별도로 실행한다.

  • 각 도메인의 UI 확인 완료(mock)실제 서버 연동 완료(server)가 Progress·추적표·알려진 제약에서 일치하는지 확인한다.

Phase 9 Gate

Goal 실행 P9-GATE: 활성 릴리스 전체의 install·type·lint·unit·mock/server E2E·build·금지 패턴을 최종 판정한다.

  • 시작 조건: P9-T1~P9-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:mock
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: mock UI 전체 journey와 실제 server integration 결과가 분리 기록되고 production mock 활성화·404 자동 fallback이 0건이다. 전체 자동 Gate는 0 failure/0 error이며 production source의 금지 값과 미완료 placeholder는 0건이다.

수동 확인: 지원 browser·viewport에서 활성 릴리스 journey, keyboard, 200% zoom, 민감정보 비기록, mock banner와 server mode를 확인하고 외부 의존 범위를 완료로 표시하지 않았는지 PRD·README·Progress를 대조한다.

Task R9.1 — inactive Character 하위 mutation 일괄 차단

Goal 실행 P9-R1: isActive=false Character workspace에서 조회만 유지하고 모든 하위 mutation 진입점과 직접 route를 차단한다.

  • 연결 리뷰: Phase 9 재리뷰REV-P9-001

  • 시작 조건: CHAR-007, PRD 7.2; backend ownership/active 검증을 대체하지 않는다.

  • Files: Modify: CharacterEditPage.tsx, Audio list/detail/form Page, Series list/detail/form/order Page와 capability hook, CommunityPostSheet.tsx, CommentThread.tsx 또는 소비 props, 관련 feature test, tests/e2e/resource-workflows.spec.ts, domain E2E.

  • Interfaces: canMutate = character.isActive && viewportCapability; false일 때 network mutation 0건, read-only list/detail/player/comment 조회는 유지.

  • 범위 밖: backend active-only 구현, 비활성 Character 복원, 모바일 capability 확대.

  • RED: inactive Character의 Character edit, Audio create/edit/comment, Series create/edit/link/order, Community comment mutation이 현재 노출/호출되는 E2E를 실패시킨다.

  • GREEN: 각 Page가 Character 응답으로 mutation capability를 계산해 form/action/direct route를 guidance/read-only 상태로 교체한다.

  • REFACTOR: viewport와 active 조건의 이름을 분리하고 boolean prop을 필요한 feature component까지만 전달한다.

  • 실행 명령: npm run test:run -- src/features/characters src/features/audio-contents src/features/series src/features/community-posts src/features/comments; npm run e2e:mock -- tests/e2e/resource-workflows.spec.ts tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/series.spec.ts tests/e2e/comments.spec.ts.

  • 기대 결과: exit 0, inactive workspace mutation request 0건, 조회 request와 read-only banner 유지.

  • 수동 확인: 320/768/1280px 직접 URL 포함 모든 도메인의 read-only action inventory를 확인한다.

Task R9.2 — field error 연결과 첫 오류 focus

Goal 실행 P9-R2: 모든 주요 form에서 오류 문구를 입력과 programmatic하게 연결하고 submit 뒤 첫 invalid control에 focus한다.

  • 연결 리뷰: Phase 9 재리뷰REV-P9-002

  • 시작 조건: PRD 10.5, 10.7; 기존 visible label과 live alert를 유지한다.

  • Files: Modify: Character/Audio/Series/Community/FanTalk/Comment form components와 tests, 필요 시 src/shared/ui의 작은 error-id/focus helper, tests/e2e/accessibility.spec.ts.

  • Interfaces: field별 stable error ID, aria-describedby 또는 aria-errormessage, aria-invalid=true, DOM 순서 첫 invalid focus.

  • 범위 밖: form library 전환, 서버 errorProperty 추정 mapping.

  • RED: 각 도메인 대표 필드의 accessible description과 multi-error 첫 focus 실패 test를 작성한다.

  • GREEN: 현재 markup에 ID/ref를 추가하고 submit validation 뒤 첫 error focus를 적용한다.

  • REFACTOR: 실제 2개 이상 소비되는 최소 helper만 shared로 올리고 axe/keyboard 회귀를 실행한다.

  • 실행 명령: npm run test:run -- src/features; npm run e2e:mock -- tests/e2e/accessibility.spec.ts.

  • 기대 결과: exit 0, invalid field 연결 누락 0건, 첫 error focus 일치, axe critical·serious 0건.

  • 수동 확인: screen reader/keyboard로 Character·Audio·Series·Community 대표 invalid submit을 확인한다.

Task R9.3 — Phase 9 과거 리뷰 상태 종결

Goal 실행 P9-R3: 완료된 P9-R1~R2를 기존 Phase 9 리뷰의 발견 상태·최종 결론·남은 항목에 누적 반영한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-003

  • 시작 조건: plan-task.md의 2026-07-30 P9-R1, P9-R2, P9-GATE 완료 증거.

  • 완료 증거: 과거 리뷰를 삭제·이름 변경하지 않고 판정 기록과 수정 후 검증을 누적하며 stale 현재 상태 검색과 Markdown link/diff 검증 통과.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/reviews/review-phase-9-20260729.md; Test: 문서 검색.

  • Interfaces: REV-P9-001~002 상태는 수정 완료, 최종 결론은 수정 검증 완료, 남은 실제 server QA는 P10 수동 QA로 구분한다.

  • 범위 밖: 2026-07-29 당시 실행 수치 삭제·재작성, 애플리케이션 코드 변경.

  • TDD 예외: 문서 상태 Task이므로 확정, P9-R1, P9-R2, 수정 goal 필요의 stale 현재 문구 검색을 RED 대체 증거로 기록한다.

  • 완료 증거를 기존 판정 기록과 §9에 날짜순으로 누적하고 과거 실패 이력은 보존한다.

  • 새 Phase 9 리뷰와 plan link, stale 현재 상태 0건, git diff --check를 검증한다.

  • 실행 명령: rg -n 'REV-P9-00[12]|P9-R[12]|수정 goal 필요' docs/20260725_AI캐릭터관리자웹/reviews/review-phase-9-20260729.md; git diff --check -- docs/20260725_AI캐릭터관리자웹.

  • 기대 결과: 과거 사실 보존, 현재 finding 2건 수정 완료, 남은 자동 Gate 오류 0건.

  • 수동 확인: 2026-07-29 판정과 2026-07-30 수정 검증이 시간순으로 구분되는지 확인한다.

Progress — 2026-07-30 P9-R3 완료

  • RED 대체: rg -n 'REV-P9-00[12]|P9-R[12]|수정 goal 필요|확정' docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md가 현재 리뷰의 REV-P9-003 확정 상태와 과거 리뷰 미해결 설명을 검출했다.
  • GREEN: review-phase-9-20260729.mdREV-P9-001~002 수정 완료 상태, P9-R1~R2P9-GATE 검증 기록, 최신 최종 결론을 누적했다. phase9-cross-cutting-quality.mdREV-P9-003 수정 완료로 정리했다.
  • REFACTOR: 과거 2026-07-29 실패·timeout 이력은 삭제하지 않고, 현재 상태 문구만 최신 검증 기준으로 분리했다.

Task R9.4 — 일반 mutation pending·실패 복구 경계 보강

Goal 실행 P9-R4: 공통 비활성화와 Series 연결·해제·순서 저장이 빠른 반복 입력에도 요청을 한 번만 보내고 실패를 화면에서 복구 가능하게 한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-004

  • 시작 조건: PRD §13의 파일 업로드 외 일반 mutation 중복 제출 차단과 공통 오류·재시도 기준.

  • 완료 증거: pending Promise 기반 빠른 이중 입력·reject 실패 test, 최소 pending guard와 오류 UI, 관련 Phase focused test·mock E2E·정적 Gate 통과.

  • Files: Modify: src/shared/ui/confirm-deactivate-dialog.tsx, src/features/characters/pages/CharacterDetailPage.tsx, src/features/audio-contents/components/AudioContentForm.tsx, src/features/series/components/SeriesForm.tsx, src/features/series/components/SeriesContents.tsx, src/features/series/pages/SeriesOrderPage.tsx, 관련 tests와 필요 시 tests/e2e/{character-workspace,audio-content,series}.spec.ts.

  • Interfaces: pending 동안 같은 mutation은 동기 guard로 1회만 실행하고 관련 controls는 disabled/aria-busy 상태를 제공한다. reject 시 dialog/page에 role=alert 오류를 표시하고 재시도를 허용한다.

  • 범위 밖: 범용 mutation state library, upload progress(P4-R2), backend idempotency key, 모든 read request debounce.

  • RED: Character/Audio/Series 비활성화와 Series 연결·해제·순서 저장의 pending Promise에서 빠른 이중 click을 보내 request 1회를 기대하는 실패 test, deactivate reject의 visible alert test를 작성한다.

  • GREEN: 각 mutation 경계에 synchronous guard와 pending state를 추가하고 공통 confirm dialog가 pending/오류 상태를 접근 가능하게 표시하도록 확장한다.

  • REFACTOR: 비활성화 dialog의 공통 UI만 공유하고 서로 다른 도메인 mutation을 새 범용 hook으로 추상화하지 않는다.

  • 실행 명령: npm run test:run -- src/shared/ui src/features/characters src/features/audio-contents src/features/series; npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/series.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, 각 대상 mutation의 pending 중 요청 최대 1회, 실패 시 unhandled rejection 0건과 visible recovery 안내.

  • 수동 확인: 느린 network에서 confirm/link/unlink/order 버튼 연타, disabled 상태, 실패 후 재시도를 keyboard-only로 확인한다.

실행 기록 — 2026-07-30

  • RED: npm run test:run -- src/shared/ui/__tests__/confirm-deactivate-dialog.test.tsx src/features/series/tests/series-order.test.tsx src/features/series/tests/series-contents.test.tsx는 공통 dialog pending/error UI와 Series 연결·해제·순서 저장 pending guard 부재로 실패했고, npm run test:run -- src/features/characters/tests/CharacterDetailPage.test.tsx src/features/audio-contents/tests/audio-form-update.test.tsx src/features/series/tests/series-form.test.tsx는 Character/Audio/Series deactivate pending label과 visible failure alert 부재로 실패했다.
  • GREEN: ConfirmDeactivateDialogisPending, errorMessage, disabled controls, aria-busy, role=alert를 추가했다. Character/Audio/Series deactivate, Series 연결·해제, Series 순서 저장 경계에 synchronous guard와 pending state를 추가해 pending 중 request 1회와 실패 후 재시도를 보장했다.
  • REFACTOR: 도메인별 mutation은 각 소유 컴포넌트에 그대로 두고, 공통화는 deactivate dialog 표시 상태에만 제한했다. 새 범용 mutation hook이나 idempotency key는 만들지 않았다.
  • 검증: npm run test:run -- src/features/characters/tests/CharacterDetailPage.test.tsx src/features/audio-contents/tests/audio-form-update.test.tsx src/features/series/tests/series-form.test.tsx src/shared/ui/__tests__/confirm-deactivate-dialog.test.tsx src/features/series/tests/series-order.test.tsx src/features/series/tests/series-contents.test.tsx 결과 6 files / 28 tests passed. 변경 test 파일 LSP diagnostics는 모두 clean이었다. E2E는 사용자 지시에 따라 P10-R5 이후 최종 실행으로 보류했다.

Task R9.5 — Series mutation pending 표시 회귀 복구

Goal 실행 P9-R5: Series 연결·해제·순서 저장의 동기 중복 guard와 별개로 운영자가 인지할 수 있는 pending 상태와 관련 control 비활성화를 복구한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-005

  • 시작 조건: PRD §10.5의 저장 중 button 비활성화·진행 표시, 300ms feedback와 완료 기록 P9-R4disabled/aria-busy interface.

  • 완료 증거: pending Promise 동안 visible 상태와 관련 control 비활성화를 기대하는 실패 test, ref guard를 보존한 reactive state 최소 수정, Series focused unit·mock E2E·정적 Gate 통과.

  • Files: Modify: src/features/series/components/SeriesContents.tsx, src/features/series/components/SeriesOrderList.tsx, src/features/series/pages/SeriesOrderPage.tsx, 관련 Series tests와 tests/e2e/series.spec.ts.

  • Interfaces: isLinkingRef/isUnlinkingRef/isSavingRef의 동기 1회 guard는 유지하고 React pending state로 link 후보·unlink dialog·order move/save control을 비활성화하며 aria-busy 또는 visible 진행 문구를 제공한다. reject 뒤 재시도를 허용한다.

  • 범위 밖: 범용 mutation hook, backend idempotency key, Series API payload 변경.

  • RED: link/unlink/order pending Promise 중 button 연타 request 1회뿐 아니라 각 관련 control의 disabled·진행 접근성 상태를 기대하는 실패 test를 작성한다.

  • GREEN: 세 mutation에 reactive pending state를 추가하고 finally에서 해제하며 dialog/order list에 필요한 prop만 전달한다.

  • REFACTOR: 동기 guard와 화면 state의 서로 다른 책임을 이름으로 구분하고 공통 hook을 만들지 않는다.

  • 실행 명령: npm run test:run -- src/features/series/tests/series-contents.test.tsx src/features/series/tests/series-order.test.tsx; npm run e2e:mock -- tests/e2e/series.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, pending 중 관련 mutation request 최대 1회와 control 조작 0회, 진행 상태 노출, 실패 뒤 재시도 성공.

  • 수동 확인: 느린 network에서 link/unlink/order save를 연타하고 disabled·진행 문구·오류 후 재시도를 keyboard-only로 확인한다.

P9-R5 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/features/series/tests/series-contents.test.tsx src/features/series/tests/series-order.test.tsx는 pending 중 link/save 버튼 disabled 기대에서 실패해 visible pending 상태 부재를 재현했다.
  • GREEN/REFACTOR: SeriesContentsisLinking/isUnlinking, SeriesOrderPageisSaving state를 추가하고, 기존 ref guard는 유지했다. SeriesOrderList에는 isDisabled prop만 전달해 순서 이동 control을 저장 중 비활성화했다. 새 범용 hook은 만들지 않았다.
  • 검증: focused 2 files / 9 tests passed, npm run test:run -- src/features/series는 8 files / 37 tests passed. npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. src/features/series LSP diagnostics는 17 files, 0 diagnostics였다. E2E는 사용자 지시에 따라 전체 Task 완료 후 필요 시 실행한다.

Task R9.6 — 일반 mutation 재진입·진행 피드백 경계 종결

Goal 실행 P9-R6: Series form, Community, FanTalk, Comments의 일반 mutation이 같은 화면에서 pending 중 재진입하지 않고 운영자와 보조기기에 진행·실패 상태를 전달하도록 한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-006

  • 시작 조건: PRD §10.5의 저장 중 button 비활성화·진행 표시와 300ms 피드백, §13의 파일 upload 외 일반 mutation 중복 제출 차단.

  • 완료 증거: pending Promise에 같은 submit/click을 연속 전달해 request 1회와 accessible progress를 기대하는 실패 test, FanTalk 미답변 원글 DELETE reject의 visible alert test, 관련 Phase focused unit·mock Chromium E2E·정적 Gate 통과.

  • Files: Modify: src/features/series/components/SeriesForm.tsx, src/features/community-posts/components/{CommunityPostForm,CommunityPostSheet}.tsx, src/features/fan-talks/components/{FanTalkReplyForm,FanTalkReplySheet}.tsx, src/features/comments/components/{CommentForm,CommentItem,CommentThread}.tsx, 관련 tests와 필요 시 도메인 E2E.

  • Interfaces: 각 mutation 경계는 state 갱신 전에도 동작하는 동기 guard로 요청을 1회만 허용한다. pending 동안 관련 control을 비활성화하고 role=status, aria-busy, 또는 처리 중 label 중 하나로 진행을 노출한다. FanTalk DELETE 실패는 답변 유무와 무관하게 visible alert와 재시도를 제공한다.

  • 범위 밖: 범용 mutation hook/state library, backend idempotency key, read request debounce, upload progress, API payload 변경.

  • Phase 9 Gate 연결: 기존 Gate·완료 기록은 보존하되 P9-R6 대상 Phase 5~8 focused test, mock Chromium Series·Community·FanTalk·Comments E2E, typecheck·lint·build 통과 전에는 이번 교차 회귀를 종결하지 않는다.

  • 검증 기록: 도메인별 RED/GREEN test 수, 중복 request count, 실패 후 재시도, E2E·정적 Gate 결과를 이 Task 하단과 Phase 9 리뷰 §9 이후에 날짜순으로 누적한다.

  • RED: Series create/update, Community create/update/fixed, Comments create/update/delete를 pending으로 두고 빠른 재입력 request 1회와 진행 상태를 기대하는 실패 test를 작성한다. FanTalk 미답변 원글 DELETE reject가 visible alert와 재시도를 제공하는 실패 test도 추가한다.

  • GREEN: 대상 handler에 최소 동기 guard를 추가하고 기존 isSaving을 disabled·accessible progress에 연결하며 FanTalk confirm dialog에 pending/error 상태를 전달한다.

  • REFACTOR: 공통화는 기존 ConfirmDeactivateDialog interface 재사용까지만 허용하고 서로 다른 mutation을 새 hook으로 통합하지 않는다.

  • 실행 명령: npm run test:run -- src/features/series src/features/community-posts src/features/fan-talks src/features/comments; npm run e2e:mock -- tests/e2e/series.spec.ts tests/e2e/community.spec.ts tests/e2e/fan-talk.spec.ts tests/e2e/comments.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build; git diff --check.

  • 기대 결과: 모든 명령 exit 0, 대상별 pending 중 mutation request 최대 1회, 진행 상태 노출, reject 후 visible 오류와 재시도 성공.

  • 수동 확인: 느린 network에서 keyboard submit·빠른 click을 반복하고 disabled·focus·상태 안내·실패 후 재시도를 각 화면에서 확인한다.

P9-R6 완료 기록 (2026-07-31):

  • 무엇을: Series form, Community create/Sheet save/fixed, Comments mutation, FanTalk 미답변 원글 DELETE에 pending 중 동기 재진입 guard와 접근 가능한 진행/실패 피드백을 추가했다.
  • 왜: REV-P9-006에서 같은 화면의 일반 mutation이 React state 반영 전 재진입하거나 실패 상태를 보조기기와 운영자에게 전달하지 못하는 경계가 확인됐기 때문이다.
  • RED: npm run test:run -- src/features/series/tests/series-form-pending.test.tsx src/features/community-posts/tests/community-mutation-pending.test.tsx src/features/comments/tests/comment-thread-pending.test.tsx src/features/fan-talks/tests/fan-talk-delete-pending.test.tsx는 구현 전 4 files / 5 tests가 기대대로 실패했다. 실패 원인은 pending role=status 부재와 FanTalk DELETE 실패 alert/재시도 미노출이었다.
  • GREEN: 같은 focused 명령은 구현 후 4 files / 5 tests passed였다. npm run test:run -- src/features/community-posts/tests/community-sheet.test.tsx src/features/community-posts/tests/community-mutation-pending.test.tsx는 2 files / 9 tests passed, npm run test:run -- src/features/series src/features/comments는 12 files / 50 tests passed, npm run test:run -- src/features/fan-talks는 4 files / 14 tests passed였다.
  • REFACTOR: 새 범용 mutation hook/state library는 만들지 않고 각 도메인 handler의 useRef guard와 기존 isSaving 상태만 연결했다. 공통화는 이미 존재하는 ConfirmDeactivateDialogisPending/errorMessage prop 재사용으로 제한했다.
  • 회귀 확인: npm run test:run -- src/features/series src/features/community-posts src/features/comments src/features/fan-talks는 120초 timeout 중 community-sheet.test.tsx 목록 로딩 실패를 1건 표시했다. 이후 실패 spec 단독, pending 인접 spec, FanTalk 단독, Series+Comments 단독 실행은 모두 통과해 넓은 병렬 실행의 비결정 로딩 실패로 분리 기록한다. npm run e2e:mock -- tests/e2e/series.spec.ts tests/e2e/community.spec.ts tests/e2e/fan-talk.spec.ts tests/e2e/comments.spec.ts --project=chromium은 24 tests passed였다. npm run typecheck, npm run lint, npm run build, 관련 파일 git diff --check는 모두 exit 0이었다.

Task R9.7 — Safari/WebKit 지원 Gate 복원 (제품 결정으로 대체됨)

Goal 실행 P9-R7: PRD와 완료된 P9-T2가 요구하는 desktop WebKit·mobile Safari Playwright project를 복원하고 네 browser project의 mock/server Gate를 다시 통과시킨다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-007

  • 시작 조건: 과거 PRD §13과 P9-T2가 요구했던 Safari/WebKit Gate 복원 이력. 2026-07-31 제품 결정 이후 현재 지원 범위는 Chromium/mobile Chrome이다.

  • 완료 증거: WebKit project 부재 실패, 두 Safari 계열 project 복원, 기존 skip은 platform capability 근거가 있는 keyboard-only case로 한정, exact full mock/server E2E와 browser support 문서 정합성 검증 기록.

  • Files: Modify: playwright.config.ts, README.md, tests/e2e/character-workspace.spec.ts; Test: tests/e2e/smoke.spec.ts, tests/e2e/auth.spec.ts, tests/e2e/accessibility-shell.spec.ts, tests/e2e/mock-preview-shell.spec.ts, tests/e2e/mock-mode-boundary.spec.ts, tests/e2e/audio-content.spec.ts, tests/e2e/series.spec.ts, tests/e2e/community.spec.ts, tests/e2e/fan-talk.spec.ts, tests/e2e/comments.spec.ts, tests/e2e/resource-workflows.spec.ts, tests/e2e/error-mapping.spec.ts, tests/e2e/responsive-capabilities.spec.ts, tests/e2e/accessibility.spec.ts.

  • Interfaces: project 이름은 기존 기록과 같은 webkit, mobile-safari를 사용한다. Safari 범위를 줄이려면 이 Task에서 임의 변경하지 않고 별도 제품 결정으로 PRD부터 갱신한다.

  • 범위 밖: Edge engine 자동화 추가, 실제 기기·최신 2개 major 전체 자동화, 테스트 실패를 숨기기 위한 project 제거나 근거 없는 skip, 제품 기능 확장.

  • Phase 9 Gate 연결: 네 project의 exact e2e:mock, server allowlist, browser support 정적 대조가 모두 통과하기 전에는 Phase 9 교차 품질 회귀를 종결하지 않는다.

  • RED: npx playwright test --list --project=webkit의 project-not-found를 기록하고 현재 README와 PRD browser matrix 불일치를 고정하는 정적 assertion을 추가한다.

  • GREEN: Desktop Safari와 iPhone Safari device project를 기존 이름으로 복원하고 검증된 app/test root cause만 최소 수정한다.

  • REFACTOR: browser별 skip을 keyboard focus 같은 문서화된 platform capability로만 제한하고 dirty-leave·mutation·contract 흐름은 네 project에서 유지한다.

  • 실행 명령: 과거 복원 당시 npx playwright install webkit; npx playwright test --list --project=webkit; npx playwright test --list --project=mobile-safari; npm run e2e:mock; npm run e2e; npm run typecheck; npm run lint; npm run build를 사용했다. 현재 검증은 Chromium/mobile Chrome만 사용한다.

  • 기대 결과: 현재 기준으로 chromium·mobile-chrome만 수집·실행되고 PRD·README·Gate matrix 불일치 0건.

  • 수동 확인: Safari/WebKit 수동 확인은 현재 지원 범위에서 제외한다.

P9-R7 수정 검증 기록 (2026-07-31):

2026-07-31 제품 결정: 이 복원 기록은 과거 이력으로만 보존한다. 현재 지원·자동 검증 범위는 Chromium/mobile Chrome이며 WebKit·Mobile Safari는 지원 범위에서 제외한다.

  • 무엇을: webkitmobile-safari Playwright project를 복원하고, README/PRD의 browser support 문구와 mock Gate 문서 정합성을 맞췄다. 긴 WebKit 계열 mock run에서 /login navigation이 누적 timeout을 내던 mock-preview-shell.spec.ts는 같은 검증을 단일 로그인 세션으로 합쳐 반복 navigation을 제거했다.
  • 왜: 당시 PRD §13과 완료된 P9-T2가 Chromium/WebKit/mobile Chrome/mobile Safari 자동 Gate를 요구하지만, 설정에는 Chromium/mobile Chrome만 남아 Phase 9 acceptance 범위가 축소됐기 때문이다. 이 사유는 과거 이력이며 현재 제품 결정은 Chromium/mobile Chrome만 지원한다.
  • 어떻게:
    • RED: npx playwright test --list --project=webkitnpx playwright test --list --project=mobile-safari는 각각 Project(s) "webkit" not found, Project(s) "mobile-safari" not found로 실패했다.
    • GREEN/REFACTOR: playwright.config.tswebkit, mobile-safari project를 복원하고 src/shared/mocks/__tests__/mock-preview-docs.test.ts에 README/PRD/Playwright project 정합성 assertion을 추가했다. tests/e2e/mock-preview-shell.spec.ts는 320px mock shell, no backend fallback, storage, axe 검증을 한 세션에서 수행하도록 정리했다.
    • Focused 검증: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts는 1 file / 4 tests passed였다. npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts --project=webkit --workers=1--project=mobile-safari --workers=1은 각각 1 passed였다. WebKit/mobile Safari의 resource/series tail focused 재실행도 0 failure였다.
    • Full Gate: 최종 npm run e2e:mock은 Chromium 52 passed, WebKit 44 passed / 8 skipped, Mobile Chrome 47 passed / 5 skipped, Mobile Safari 43 passed / 9 skipped로 전체 0 failure였다. skipped 항목은 기존 browser 조건에 맞춘 keyboard-only 또는 Chromium 전용 audio metadata case다. npm run e2e는 server allowlist 36 passed였고, npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다.
    • 정적 검증: package.json JSON parse 확인은 package.json ok였고, 당시 관련 문서·설정의 browser support 문구는 4-project 자동 Gate와 Edge/실기기 릴리스 QA 분리를 유지했다. 현재 제품 결정은 Chromium/mobile Chrome만 지원한다.
  • 남은 항목: 없음. 실제 Edge, desktop Safari, iOS Safari 물리 브라우저/기기 QA는 현재 지원 범위에서 제외한다.

Task R9.8 — Mock E2E 실행 계약·문서·unit Gate 정합성 복구

Goal 실행 P9-R8: P9-R7에서 도입한 bare/filtered mock E2E 실행 방식을 공개 문서와 mode contract test에 일치시키고 전체 unit Gate를 복구한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-008

  • 시작 조건: P9-R7 완료 기록, 현재 bare npm run e2e:mock의 Chromium/mobile Chrome 분할 실행과 인자 전달 시 focused Playwright 실행 계약.

  • 완료 증거: 현재 focused 2-test 실패 재현, public script semantics·README·agent guide·contract test 최소 정렬, 전체 unit 및 P9-GATE 재검증 기록.

  • Files: Modify: package.json(실행 계약을 단순화할 때만), README.md, docs/agent-guide/scripts.md, src/shared/mocks/__tests__/mode-boundary.test.ts, src/shared/mocks/__tests__/mock-preview-docs.test.ts; Test: 기존 두 contract test와 전체 unit/E2E Gate.

  • Interfaces: 인자 없는 npm run e2e:mock은 Chromium/mobile Chrome matrix를 실행한다. file/project 인자가 있으면 Playwright에 그대로 전달해 testMatch와 교집합만 실행한다. VITE_API_MODE=mock, server command와 no-auto-fallback 경계는 유지한다.

  • 범위 밖: 제품 코드 변경, 문서에 내부 shell 문자열 전체를 중복해 유지하는 새 결합.

  • Phase 9 Gate 연결: focused contract test, 전체 unit, mock/server E2E, typecheck·lint·build가 모두 통과하기 전에는 완료하지 않는다.

  • RED: mode-boundary.test.ts의 direct script 기대와 mock-preview-docs.test.ts의 README actual-script 기대가 현재 e2e:mock wrapper와 각각 실패하는 것을 기록했다.

  • GREEN: bare/full과 filtered/focused의 공개 동작을 한 기준으로 문서화하고 contract test가 내부 구현 문자열이 아니라 해당 동작과 mock mode 경계를 검증하게 했다.

  • REFACTOR: 기존 분할 script의 이름과 책임만 유지하고 새 runner·dependency를 추가하지 않았으며, 중복 명령 문자열을 test/문서에 복제하지 않았다.

  • focused·전체 unit과 정적 Gate를 재실행하고 RED/GREEN/REFACTOR 및 실제 pass 수를 Progress에 누적했다. E2E full matrix는 직전 요청 기준 재리뷰의 pass 기록과 filtered 인자 전달 surface를 재사용하고, 사용자 지시에 따라 개발 중 반복 실행하지 않았다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run test:run; npm run e2e:mock -- --list --project=chromium; npm run e2e:mock; npm run e2e; npm run typecheck; npm run lint; npm run build; git diff --check.

  • 기대 결과: 모든 명령 exit 0, unit 0 failure, Chromium/mobile Chrome mock matrix 0 failure, server allowlist 0 failure, README·agent guide와 실제 bare/filtered 동작 불일치 0건.

  • 수동 확인: README와 docs/agent-guide/scripts.md만 읽고 전체 matrix 실행과 focused filter 전달 방법을 구분할 수 있는지 확인한다.

P9-R8 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 failed / 5 passed. mode-boundary.test.ts는 direct VITE_API_MODE=mock playwright test 기대와 wrapper 차이로 실패했고, mock-preview-docs.test.ts는 README에 내부 wrapper 전체 문자열이 없어서 실패했다.
  • GREEN/REFACTOR: mode-boundary.test.ts는 공개 실행 의미(VITE_API_MODE=mock playwright test "$@" 인자 전달, 4-project 분할 script, raw direct script)를 검증하게 바꿨다. mock-preview-docs.test.ts, README.md, docs/agent-guide/scripts.mdnpm run e2e:mock의 bare full matrix와 filtered file/--project 전달 의미를 문서화했다. 새 runner·dependency와 Safari 범위 축소는 추가하지 않았다.
  • focused/surface: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 7 tests passed. npm run e2e:mock -- --list --project=chromium — 52 tests collected.
  • unit Gate: 첫 npm run test:run은 240초 timeout 중 production-graph.test.tsaudio-list.test.tsx 진행 실패를 표시했다. production-graph.test.ts는 단독 1 passed였고, audio-list.test.tsx는 route/data settling 대기 timeout을 명시해 단독 2 passed로 복구했다. 이후 full npm run test:runcommunity-sheet.test.tsx 5초 timeout 1건만 남았고, 단독 7 passed 확인 뒤 해당 integration test timeout만 10_000으로 명시했다. 최종 npm run test:run — 78 files / 394 tests passed.
  • 정적 검증: npm run typecheck, npm run lint, git diff --check -- README.md docs/agent-guide/scripts.md src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts, git diff --check -- src/features/community-posts/tests/community-sheet.test.tsx src/features/audio-contents/tests/audio-list.test.tsx는 모두 exit 0이었다. LSP diagnostics는 변경 test 파일에서 3초 제한 timeout이 발생해 typecheck로 보완했다.

Task R9.9 — 전체 unit Gate 비결정적 대기·정리 복구

Goal 실행 P9-R9: 단독 focused 실행은 통과하지만 전체 Vitest 실행에서 임의의 integration spec이 loading 또는 1/5초 대기에 머무는 비결정성을 제거한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-009

  • 시작 조건: 2026-07-31 fresh npm run test:run 두 차례에서 Community Sheet·Character workspace가 반복 실패하고 Series route·Character mutation 실패 집합은 변했으며, 각 spec focused 실행은 통과한 증거.

  • 완료 증거: full-suite failure를 의도적으로 재현하는 순서/대기 증거, 정확한 root cause의 최소 test cleanup·async settling 수정, 관련 spec focused 통과와 전체 npm run test:run 2회 연속 0 failure 기록.

  • Files: Modify: src/features/community-posts/tests/community-sheet.test.tsx, src/features/series/tests/series-routes.test.tsx, src/layouts/CharacterWorkspaceLayout.test.tsx, src/features/characters/tests/character-mutation-reload.test.tsx, src/features/fan-talks/tests/fan-talk-reply.test.tsx; root cause가 공통 정리임이 입증된 경우만 Modify: src/shared/test/setup.ts, src/app/app-test-support.ts.

  • Interfaces: integration test는 request/response 준비 완료를 명시적으로 await하고 다음 test로 pending request, timer, browser history listener를 넘기지 않는다. assertion 커버리지와 production timeout은 변경하지 않는다.

  • 범위 밖: 전역 timeout 일괄 상향, 실패 spec skip·삭제, fileParallelism 축소로 가림, 제품 코드 임의 변경. 제품 root cause가 확정되면 이 Task에서 숨기지 않고 소유 Phase 신규 리뷰 Task로 분리한다.

  • Phase 9 Gate 연결: 실패 4개 spec focused, 전체 unit 2회 연속, typecheck·lint·build, mock/server smoke가 모두 통과하기 전에는 완료하지 않는다.

  • RED: full suite 실패 순서와 pending request·timer·listener·React update를 축소한 명령으로 고정하고, focused 단독 통과와 전체 실패 차이를 기록한다.

  • GREEN: 입증된 root cause의 await/cleanup/handler 격리만 최소 수정해 축소 재현 명령과 focused spec을 통과시킨다.

  • REFACTOR: 수행 시간을 숨기기 위한 전역 timeout·skip이 없는지 확인하고 full suite를 2회 연속 실행한다.

  • RED/GREEN/REFACTOR, focused·full 2회·정적/build 결과를 Progress와 Phase 9 리뷰에 누적한다. 개발 중 E2E smoke는 사용자 지시에 따라 모든 신규 Task 구현 후 필요 시 실행한다.

  • 실행 명령: npm run test:run -- src/features/community-posts/tests/community-sheet.test.tsx src/features/series/tests/series-routes.test.tsx src/layouts/CharacterWorkspaceLayout.test.tsx src/features/characters/tests/character-mutation-reload.test.tsx src/features/fan-talks/tests/fan-talk-reply.test.tsx; npm run test:run; npm run test:run; npm run typecheck; npm run lint; npm run build; npm run e2e:mock -- tests/e2e/community.spec.ts tests/e2e/series.spec.ts tests/e2e/character-workspace.spec.ts tests/e2e/fan-talk.spec.ts --project=chromium; npm run e2e -- tests/e2e/auth.spec.ts tests/e2e/server-mode-boundary.spec.ts.

  • 기대 결과: focused·전체 2회·정적/build·smoke 명령 exit 0, full unit failure 0건, touched spec의 예기치 않은 MSW unhandled request·React act warning 0건.

  • 수동 확인: 없음. 자동 test harness 결정성 복구 Task이며 화면 기능은 관련 mock/server smoke로 대체 확인한다.

P9-R9 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/features/characters/tests/character-mutation-reload.test.tsx가 보호 route probe 완료 전 screen.getByLabelText("이름")를 호출해 실패했다. 실패 화면은 보호 route 확인 중 상태였다.
  • GREEN: create route도 P1-R14 이후 보호 probe를 거치므로 첫 form field를 await screen.findByLabelText("이름")로 기다리게 했다. 제품 코드는 변경하지 않았다.
  • 검증: focused npm run test:run -- src/features/characters/tests/character-mutation-reload.test.tsx는 1 file / 3 tests passed. 전체 npm run test:run은 2회 연속 81 files / 409 tests passed였다. 변경 test 파일 LSP diagnostics는 0건이었다.
  • 범위 유지: 전역 timeout 상향, spec skip, file parallelism 축소, 제품 코드 임의 변경은 하지 않았다. E2E smoke는 개발 중 반복 실행하지 말라는 지시에 따라 P9-R10 이후 필요 시 최종 Gate에서 실행한다.

Task R9.10 — mock E2E navigation·mutation 대기 결정성 복구

Goal 실행 P9-R10: Character 직접 route navigation과 FanTalk mutation 성공 피드백이 간헐적으로 경합하는 비결정성을 Chromium/mobile Chrome 지원 범위에서 제거한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-010

  • 시작 조건: P9-R9 완료, fresh bare npm run e2e:mock의 WebKit main 2 failed / 32 passed / 7 skipped과 동일 2-test focused WebKit 2 passed 증거.

  • 완료 증거: login→direct route과 FanTalk PUT→success/refetch 경합의 재현 순서, 명시적 navigation/mutation settling 최소 수정, focused Chromium·Mobile Chrome E2E와 정적 검증 통과 기록. 2026-07-31 사용자 지시에 따라 WebKit·Mobile Safari E2E는 실행하지 않는다.

  • Files: Modify: tests/e2e/character-workspace.spec.ts, tests/e2e/fan-talk.spec.ts; 동일 login helper 경합이 다른 spec에서도 재현될 때만 Modify: tests/e2e/accessibility.spec.ts, tests/e2e/audio-content.spec.ts, tests/e2e/comments.spec.ts, tests/e2e/community.spec.ts, tests/e2e/resource-workflows.spec.ts, tests/e2e/responsive-capabilities.spec.ts, tests/e2e/series.spec.ts.

  • Interfaces: login helper는 URL 변경만이 아니라 보호 route probe·목록 ready까지 완료된 뒤 다음 page.goto를 허용한다. mutation test는 request, visible success, refetch/navigation 순서를 명시적으로 대기하고 중간 상태를 timeout 우연에 의존하지 않는다.

  • 범위 밖: 근거 없는 browser project 제거, failing test skip, retry만 추가, 전역 timeout 상향, 화면 기능 축소. 제품 root cause가 재현되면 소유 Phase 신규 리뷰 Task로 분리한다.

  • Phase 9 Gate 연결: 두 test의 Chromium·Mobile Chrome focused E2E, typecheck·lint·build가 모두 통과하기 전에는 완료하지 않는다. WebKit·Mobile Safari matrix는 2026-07-31 사용자 지시에 따라 제외한다.

  • RED: 과거 WebKit bare 실패 기록과 현재 focused/repeat 통과를 대조해 login ready·mutation/refetch 대기 경계가 약한 지점을 기록한다.

  • GREEN: 보호 route ready와 mutation/refetch 순서를 명시적으로 await하는 최소 test 수정을 적용해 focused Chromium·Mobile Chrome을 통과시킨다.

  • REFACTOR: 동일 login helper 중복은 이번 재현 범위에서 공통화하지 않고, test retry·skip·timeout 완화 없이 변경 파일만 검증한다.

  • RED/GREEN/REFACTOR, Chromium·Mobile Chrome focused E2E와 정적/build 결과를 Progress와 Phase 9 리뷰에 누적한다.

  • 실행 명령: npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/fan-talk.spec.ts --project=chromium --grep "direct create and edit routes|FanTalk mock journey"; 같은 명령의 --project=mobile-chrome; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: Chromium/mobile Chrome focused 합계 4 passed, 정적/build exit 0, interrupted navigation·성공 status 미발견 0건.

  • 수동 확인: 없음. 자동 E2E 대기 결정성 Task이며 모든 화면 기능은 기존 E2E assertion을 보존해 확인한다.

P9-R10 수정 검증 기록 (2026-07-31):

  • RED 대체: 기존 bare WebKit 실패는 login 직후 direct page.goto interruption과 FanTalk PUT 뒤 성공 status 대기 경합이었다. 현재 focused WebKit 단발 2 tests와 --repeat-each=5 10 tests는 모두 통과해 제품 기능 실패가 아니라 E2E 대기 경계 문제로 분리했다.
  • GREEN: 두 spec의 loginThroughMockMode()/ai-characters URL뿐 아니라 AI 캐릭터 heading까지 기다리게 했다. FanTalk 생성 답변 수정 뒤에는 dialog close 후 list-backed 첫 번째 응원입니다. 답변 보기 버튼이 보일 때까지 기다리게 했다.
  • 검증: 사용자 지시에 따라 WebKit·Mobile Safari는 추가 실행하지 않았다. npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/fan-talk.spec.ts --project=chromium --grep "direct create and edit routes|FanTalk mock journey"는 2 passed, 같은 명령의 --project=mobile-chrome도 2 passed였다. 두 변경 spec LSP diagnostics는 0건, pure LOC는 각각 187이었다. npm run typecheck, npm run lint, targeted git diff --check는 모두 통과했다.
  • 범위 유지: retry, skip, timeout 상향, WebKit/Mobile Safari project 제거 또는 제품 코드 변경은 하지 않았다.

Task R9.11 — Chromium-only Gate의 현재 문서 계약 복구

Goal 실행 P9-R11: 과거 네 browser project 실행 이력은 보존하되 PRD Decision Log와 현재 Task·최신 리뷰 결론을 Chromium/mobile Chrome 제품 결정과 실제 smoke spec 경로에 맞춘다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-011

  • 시작 조건: PRD §13, plan 상단 범위·P9-T2, playwright.config.ts, package.json, contract test는 Chromium/mobile Chrome만 요구하지만 PRD Decision Log에 변경 결정이 없고 P9-R8, P9-R10과 Phase 9 리뷰 최신 결론은 4-project/WebKit 명령을 현재 계약처럼 유지하며 P9-R9 smoke 명령은 존재하지 않는 server-boundary.spec.ts를 가리키는 불일치.

  • 완료 증거: 날짜·사유·영향 범위를 포함한 PRD Decision Log 행, 현재 Task의 WebKit project 명령·4-project 기대 0건, server-mode-boundary.spec.ts 실제 경로, 과거 실행 기록 보존, mock 104개·server 18개가 Chromium/mobile Chrome에서만 수집되는 증거와 docs contract test 통과 기록.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md, src/shared/mocks/__tests__/mock-preview-docs.test.ts.

  • Interfaces: P9-R7~R10의 과거 RED/GREEN·pass 수는 삭제하지 않고 당시 이력으로 표시한다. 현재 실행 명령·기대 결과·최신 결론만 Chromium/mobile Chrome으로 정정하고 smoke file filter는 실제 server-mode-boundary.spec.ts를 사용한다.

  • 범위 밖: E2E spec·제품 코드 변경, WebKit/mobile Safari 재설치·실행, 과거 검증 기록 삭제.

  • TDD 예외 사유: 실행 코드 변경이 없는 문서 계약 정정이므로 제품 test RED 대신 현재 Task 본문과 executable config의 불일치 검색을 RED 대체 증거로 사용한다.

  • PRD Decision Log에 2026-07-31 Chrome-only 결정의 사유와 Playwright·QA 영향 범위를 추가한다.

  • P9-R8, P9-R10의 현재 실행 명령·기대 결과와 Phase 9 최신 결론을 Chromium/mobile Chrome 기준으로 정정한다.

  • P9-R9 server smoke spec 경로를 실제 server-mode-boundary.spec.ts로 고치고, 과거 네 project 결과는 당시 이력으로 명시해 현재 Gate나 남은 위험으로 읽히는 표현을 제거한다.

  • docs contract가 현재 Task block의 WebKit/mobile Safari 명령·네 project 기대와 잘못된 smoke spec 경로를 탐지하도록 보강한다.

  • docs contract 결과를 Progress와 Phase 9 리뷰에 누적했다. Playwright 수집 목록·targeted diff는 P10-R10까지 완료한 뒤 통합 검증에서 실행한다.

  • 대체 검증 방법: 현재 Task block의 browser 명령 대조, docs contract unit, Playwright --list, Markdown link와 diff 검사.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run e2e:mock -- --list; npm run e2e -- --list; git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md.

  • 기대 결과: unit 2 files / 8 tests, mock 104 tests와 server 18 tests가 chromium·mobile-chrome에서만 수집되고 현재 Task 명령·최신 결론의 WebKit/mobile Safari 요구 0건, link·diff 오류 0건.

  • 수동 확인: P9-R7~R11과 Phase 9 리뷰 마지막 절만 읽어 과거 네 project 이력과 현재 2-project Gate를 혼동하지 않는지 확인한다.

P9-R11 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts — 1 failed / 4 passed. 신규 docs contract가 현재 P9-R8~R11 Task 정의의 mock matrix 4 projects, --project=webkit, 잘못된 server-boundary.spec.ts 경로를 검출했다.
  • GREEN/REFACTOR: PRD Decision Log에 Chrome-only 자동 Gate 결정을 추가하고, 현재 Task 정의와 Phase 9 최신 결론은 Chromium/mobile Chrome·server-mode-boundary.spec.ts 기준으로 정정했다. 과거 네 browser project pass/failure 기록은 검증 기록 이력으로 보존했다.
  • 검증: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 8 tests passed. npm run e2e:mock -- --list — Chromium/mobile Chrome 104 tests 수집. npm run e2e -- --list — Chromium/mobile Chrome 18 tests 수집. 실제 E2E 실행은 사용자 지시에 따라 생략했다.

Task R9.12 — Chromium-only 문서 계약 종료 증거 보강

Goal 실행 P9-R12: Chrome-only 결정 사유, 현재 Phase 9 문구·리뷰 상태와 docs contract의 정확한 section 범위를 하나의 재현 가능한 종료 증거로 맞춘다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-012

  • 시작 조건: PRD Decision Log의 날짜·영향은 있으나 지원 축소 사유가 없고, P9-R10 수동 확인의 “자동 WebKit harness”, Phase 9 리뷰 상단의 수정 필요 상태, P9-R11 기대 7 tests 대비 실제 8 tests와 docs contract section 오탐 가능성이 남은 근거.

  • 완료 증거: PRD 결정의 날짜·사유·영향 범위, 현재 Phase 9 Task·리뷰 metadata의 Chromium/mobile Chrome 기준, P9-R9 block 내부 실제 smoke path와 정확히 닫힌 최신 결론 section을 검증하는 docs contract 2 files / 8 tests.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md, src/shared/mocks/__tests__/mock-preview-docs.test.ts.

  • Interfaces: 과거 WebKit/Mobile Safari 실행 이력과 finding 근거는 삭제하지 않는다. 현재 Task의 실행·수동 확인, 리뷰 metadata와 contract 대상 section만 정정한다.

  • 범위 밖: Playwright project·제품/E2E 코드 변경, WebKit/mobile Safari 실행, 과거 이력 재작성.

  • Phase 9 Gate 연결: docs contract, Playwright 두 수집 목록, Markdown link·diff 검사가 통과하기 전에는 완료하지 않는다.

  • RED: current section만 검사하도록 contract를 좁혀 PRD 사유 누락, 자동 WebKit harness, 부정확한 smoke path assertion과 stale review metadata 실패를 확인했다.

  • GREEN: PRD Decision Log에 사용자 결정 사유를 추가하고 현재 Task·리뷰 metadata·기대 test 수를 실제 Chromium/mobile Chrome 계약에 맞췄다.

  • REFACTOR: sectionBetween/current section helper로 P9-R9 내부 실제 path와 최신 결론 block만 검사하고 과거 이력 문자열과 plan 전체의 우연한 match에 의존하지 않게 했다.

  • docs contract·Playwright list·link·diff 결과를 Progress와 Phase 9 리뷰에 누적했다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run e2e:mock -- --list; npm run e2e -- --list; node -e 'const fs=require("fs"),path=require("path");const root="docs/20260725_AI캐릭터관리자웹";const files=[];const walk=d=>{for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):e.name.endsWith(".md")&&files.push(p)}};walk(root);const broken=[];for(const f of files){const s=fs.readFileSync(f,"utf8");for(const m of s.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)){const t=m[1].trim().replace(/^<|>$/g,"").split("#")[0];if(t&&!/^(https?:|mailto:)/.test(t)&&!fs.existsSync(path.resolve(path.dirname(f),t)))broken.push(f+": "+t)}}if(broken.length){console.error(broken.join("\n"));process.exit(1)}'; git diff --check.

  • 기대 결과: 2 files / 8 tests, mock 104·server 18 tests가 chromium·mobile-chrome에서만 수집되고 현재 문서의 WebKit harness·잘못된 smoke path·stale review metadata·Decision Log 사유 누락 0건.

  • 수동 확인: Phase 9 현재 Task와 리뷰 마지막 두 section만 읽어 과거 Safari 이력과 현재 Chrome-only Gate를 혼동하지 않는지 확인한다.

P9-R12 수정 검증 기록 (2026-07-31):

  • RED: 강화한 mock-preview-docs.test.ts가 PRD Decision Log의 사용자 결정 사유 누락과 P9-R10 현재 수동 확인의 자동 WebKit harness 문구를 검출했다. npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts — 1 failed / 3 passed / 1 not reached.
  • GREEN/REFACTOR: PRD 결정 행에 사용자 직접 지시 사유를 추가하고, 현재 P9-R10 수동 확인 문구와 P9-R11 기대 test 수를 Chromium/mobile Chrome 계약에 맞췄다. docs contract는 P9-R9 block과 최신 Phase 9 종료 판정만 검사하도록 좁혔다.
  • 검증: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 8 tests passed. npm run e2e:mock -- --list — Chromium/mobile Chrome 104 tests 수집. npm run e2e -- --list — Chromium/mobile Chrome 18 tests 수집. Markdown link와 diff 검사는 P10-R11 완료 뒤 통합 실행한다.

Task R9.13 — Chrome-only 결정 사유·문서 section 계약 폐쇄

Goal 실행 P9-R13: Chrome-only 결정의 실제 제품·시간 근거를 Decision Log에 남기고 docs contract가 PRD 결정과 최신 Phase 9 결론의 정확한 Markdown section만 검증하게 한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-013

  • 시작 조건: P9-R12의 실행 결과는 통과하지만 PRD 결정 사유가 “사용자 직접 지시”에 머물고, docs contract가 PRD 전체와 Phase 9 리뷰의 특정 heading부터 EOF를 검사해 후속 기록의 우연한 문자열에 의존할 수 있다는 재리뷰 근거.

  • 완료 증거: Chrome 2종만 지원하며 불필요한 Safari 계열 실행이 테스트 시간을 늘린다는 Decision Log 근거, heading level 또는 명시적 경계로 닫힌 PRD Decision Log·최신 Phase 9 결론 검사, docs contract 2 files / 8 tests와 두 Playwright 목록 결과.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md, src/shared/mocks/__tests__/mock-preview-docs.test.ts.

  • Interfaces: Playwright config·project 이름·E2E spec은 변경하지 않는다. 과거 browser 실행 이력은 보존하고 현재 Decision Log와 최신 종료 판정만 범위화한다.

  • 범위 밖: WebKit/mobile Safari 설치·실행·지원 복원, 제품/E2E 코드 변경, 새 Markdown parser dependency.

  • Phase 9 Gate 연결: docs contract, mock/server --list, Markdown link·diff 검사가 모두 통과하기 전에는 완료하지 않는다.

  • RED: PRD Decision Log와 최신 Phase 9 review/종료 판정을 heading 경계로 추출하는 contract를 먼저 작성해 현재 사유 누락과 EOF scope 의존으로 실패하는지 확인한다.

  • GREEN: Decision Log에 지원 대상과 테스트 시간 근거를 추가하고 최소 section helper로 현재 두 block만 검증해 focused test를 통과시킨다.

  • REFACTOR: plan 전체·후속 review 문자열에 의존하는 assertion을 제거하고 기존 8개 docs contract 회귀를 확인한다.

  • focused·Playwright list·link·diff 결과를 Progress와 Phase 9 리뷰에 누적한다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run e2e:mock -- --list; npm run e2e -- --list; node -e 'const fs=require("fs"),path=require("path");const root="docs/20260725_AI캐릭터관리자웹";const files=[];const walk=d=>{for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):e.name.endsWith(".md")&&files.push(p)}};walk(root);const broken=[];for(const f of files){const s=fs.readFileSync(f,"utf8");for(const m of s.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)){const t=m[1].trim().replace(/^<|>$/g,"").split("#")[0];if(t&&!/^(https?:|mailto:)/.test(t)&&!fs.existsSync(path.resolve(path.dirname(f),t)))broken.push(f+": "+t)}}if(broken.length){console.error(broken.join("\n"));process.exit(1)}'; git diff --check.

  • 기대 결과: 2 files / 8 tests, mock 104·server 18 tests가 chromium·mobile-chrome에서만 수집되고 Decision Log 사유·section 경계·link·diff 오류 0건.

  • 수동 확인: PRD Decision Log와 Phase 9 마지막 review section만 읽어 지원 대상, 제외 사유, 테스트 시간 영향과 현재 Gate를 재구성할 수 있는지 확인한다.

P9-R13 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts — 1 file / 2 failed / 3 passed. PRD 결정 행의 Chrome 2종·테스트 시간 근거 누락과 ## 22. P9-R13 수정 결과 검증 section 부재를 검출했다.
  • GREEN/REFACTOR: PRD Decision Log에 Chrome 2종 지원 범위와 Safari 계열 실행의 테스트 시간 영향을 추가했다. docs contract는 PRD Decision Log의 단일 Chrome-only 결정 행과 Phase 9의 P9-R13 section만 검사하도록 닫았고, Phase 9 리뷰 metadata와 종료 판정을 REV-P9-013/P9-R13 완료로 갱신했다.
  • 검증: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts — 1 file / 5 tests passed. npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 8 tests passed. npm run e2e:mock -- --list — Chromium/mobile Chrome 104 tests 수집. npm run e2e -- --list — Chromium/mobile Chrome 18 tests 수집. Markdown link 검사와 git diff --check는 통과했다.

Task R9.14 — Community Sheet focus 반환 full-unit 경합 제거

Goal 실행 P9-R14: Community Sheet의 중첩 비활성화 dialog focus 반환 test가 전체 Vitest 부하에서도 실제 effect 완료를 기다려 full unit Gate를 결정적으로 통과하게 한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-014

  • 시작 조건: fresh full unit 1회가 community-sheet.test.tsx:82의 deactivate trigger focus assertion에서 1 failed / 416 passed였고, 동일 test 단독 5회와 다음 fresh full 417/417은 통과한 비결정성 근거.

  • 완료 증거: focus 복귀 effect를 기다리는 최소 test 수정, 동일 test 반복 5회와 full unit 2회 연속 통과, 정적/build 회귀 기록.

  • Files: Modify: src/features/community-posts/tests/community-sheet.test.tsx, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md; Reuse: src/shared/ui/use-modal-focus.ts, src/shared/ui/__tests__/confirm-deactivate-dialog.test.tsx.

  • Interfaces: 제품 focus 구현과 dialog API는 변경하지 않는다. 공통 dialog test가 사용하는 waitFor 패턴을 재사용하고 timeout 상향·retry로 실패를 숨기지 않는다.

  • 범위 밖: modal architecture 변경, 제품 Community mutation 흐름 변경, WebKit/mobile Safari 실행.

  • Phase 9 Gate 연결: focused 반복과 full unit 2회, typecheck·lint·개발/운영 build가 통과하기 전에는 완료하지 않는다.

  • RED: fresh full unit의 닫기 focus 수신 실패와 동일 test 단독 반복 통과를 기록해 full-load effect 경합을 재현했다.

  • GREEN: 취소 직후 trigger focus assertion을 effect cleanup 완료까지 기다리는 기존 waitFor 패턴으로 최소 수정했다.

  • REFACTOR: 임의 sleep·timeout·retry 없이 focused 5회와 full unit 2회를 연속 실행해 focus 경합이 재발하지 않는지 확인했다.

  • 정적/build 결과와 실제 pass 수를 Progress와 Phase 9 리뷰에 누적했다.

  • 실행 명령: for run_index in {1..5}; do npm run test:run -- src/features/community-posts/tests/community-sheet.test.tsx -t 'Community Sheet edits pins unpins and soft deletes from the list item without detail GET' || exit 1; done; npm run test:run; npm run test:run; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; git diff --check.

  • 기대 결과: focused 5회 모두 1 passed / 6 skipped, full unit 2회 모두 81 files / 417 tests, 정적/build/diff exit 0, 취소 후 trigger 외 focus 수신 0건.

  • 수동 확인: 없음. 기존 제품 focus 동작은 변경하지 않고 test의 effect 완료 관찰만 교정한다.

P9-R14 수정 검증 기록 (2026-07-31):

  • RED: 2026-07-31 재리뷰에서 fresh full unit 1회가 community-sheet.test.tsx의 취소 후 trigger focus assertion에서 1 failed / 416 passed로 실패했고, 동일 test focused 5회와 다음 fresh full은 통과해 full-load effect 경합으로 확정했다.
  • GREEN: 제품 focus 구현과 dialog API는 변경하지 않고 src/features/community-posts/tests/community-sheet.test.tsx의 취소 후 focus assertion만 공통 ConfirmDeactivateDialog test와 같은 waitFor 패턴으로 변경했다.
  • REFACTOR/검증: for run_index in {1..5}; do npm run test:run -- src/features/community-posts/tests/community-sheet.test.tsx -t 'Community Sheet edits pins unpins and soft deletes from the list item without detail GET' || exit 1; done — 5회 모두 1 passed / 6 skipped. npm run test:run 2회 — 각각 81 files / 417 tests passed. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. git diff --check -- src/features/community-posts/tests/community-sheet.test.tsx — no output. build는 기존 500kB chunk warning만 표시했다.

Task R9.15 — PRD·Phase 9 종료 판정 heading 경계 고정

Goal 실행 P9-R15: docs contract가 PRD Decision Log H2와 최신 Phase 9 종료 판정 H3를 동일·상위 heading에서 각각 닫아 후속 section 문자열로 통과할 수 없게 한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-015

  • 시작 조건: P9-R13에서 sectionAtHeading을 추가했지만 PRD Decision Log는 sectionFrom()으로 EOF까지 읽고, 최신 종료 판정도 상위 H2 block 끝까지 읽어 후속 H2/H3로 완료 문자열을 옮겨도 current assertion이 통과하는 negative-control 근거.

  • 완료 증거: heading 행을 정확히 anchor하고 동일·상위 level의 다음 heading에서 닫는 최소 helper, PRD H2·Phase 9 H2·종료 판정 H3 각각의 synthetic trailing-section negative test와 docs contract 2 files / 8 tests.

  • Files: Modify: src/shared/mocks/__tests__/mock-preview-docs.test.ts, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md.

  • Interfaces: 새 Markdown parser dependency를 추가하지 않고 문자열·정규식 helper 하나를 재사용한다. PRD 내용, Playwright config와 E2E spec은 변경하지 않는다.

  • 범위 밖: Markdown 전체 parser 구현, 과거 review 이력 삭제, WebKit/mobile Safari 실행.

  • Phase 9 Gate 연결: negative-control과 docs contract, 두 Playwright --list, link·diff 검사가 통과하기 전에는 완료하지 않는다.

  • RED: Decision Log 행을 후속 H2로, 완료 문구를 후속 H3로 옮긴 synthetic 문서가 현재 helper에서 통과하는 실패 test를 추가했다.

  • GREEN: heading level을 계산해 동일·상위 level의 다음 heading 또는 EOF에서 닫는 최소 helper로 PRD H2·최신 H2·종료 판정 H3를 추출했다.

  • REFACTOR: sectionFrom과 중복 helper를 제거하고 현재 Decision Log·Phase 9 종료 판정 assertion을 정확한 section 내부로 한정했다.

  • docs contract·Playwright list·link·diff 결과를 Progress와 Phase 9 리뷰에 누적했다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run e2e:mock -- --list; npm run e2e -- --list; node -e 'const fs=require("fs"),path=require("path");const root="docs/20260725_AI캐릭터관리자웹";const files=[];const walk=d=>{for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):e.name.endsWith(".md")&&files.push(p)}};walk(root);const broken=[];for(const f of files){const s=fs.readFileSync(f,"utf8");for(const m of s.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)){const t=m[1].trim().replace(/^<|>$/g,"").split("#")[0];if(t&&!/^(https?:|mailto:)/.test(t)&&!fs.existsSync(path.resolve(path.dirname(f),t)))broken.push(f+": "+t)}}if(broken.length){console.error(broken.join("\n"));process.exit(1)}'; git diff --check.

  • 기대 결과: 2 files / 8 tests, synthetic 후속 H2/H3 false positive 0건, mock 104·server 18 tests가 chromium·mobile-chrome에서만 수집되고 link·diff 오류 0건.

  • 수동 확인: PRD Decision Log와 Phase 9 최신 종료 판정 뒤에 임의의 같은 수준 section이 추가돼도 현재 계약 판정이 바뀌지 않는지 확인한다.

P9-R15 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts — 1 file / 1 failed / 4 passed. synthetic 후속 H3의 수정 완료 문구가 ### 종료 판정 section에 섞이는 실패를 확인했다.
  • GREEN: sectionAtHeading이 heading level을 계산해 동일·상위 level의 다음 heading에서 닫도록 수정하고, PRD Decision Log와 Phase 9 최신 종료 판정을 같은 helper로 추출하게 했다. 날짜 suffix가 있는 실제 heading은 prefix 뒤 공백 suffix만 허용했다.
  • REFACTOR/검증: sectionFrom을 제거하고 synthetic 후속 H2/H3 negative assertion을 현재 docs contract에 남겼다. npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 8 tests passed. npm run e2e:mock -- --list — Chromium/mobile Chrome 104 tests 수집. npm run e2e -- --list — Chromium/mobile Chrome 18 tests 수집. Markdown link 검사와 targeted git diff --check는 no output이었다.

Task R9.16 — 최신 Phase 9 heading exact anchor 복구

Goal 실행 P9-R16: docs contract가 임의 suffix heading을 목표 heading으로 오인하지 않고 최신 Phase 9 review H2와 그 내부 종료 판정만 검사하게 한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-016

  • 시작 조건: sectionAtHeading()line.startsWith(`${heading} `)를 허용해 임의 suffix heading을 선택하고, Phase 9 contract가 현재 최신 H2가 아닌 과거 ## 22. P9-R13 수정 결과 검증을 계속 검사하는 negative-control 근거.

  • 완료 증거: 전체 heading 행 exact match, 최신 Phase 9 H2와 그 내부 ### 종료 판정 독립 추출, 임의 suffix·후속 H2/H3 false positive를 막는 synthetic assertion과 docs contract 통과.

  • Files: Modify: src/shared/mocks/__tests__/mock-preview-docs.test.ts, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md.

  • Interfaces: 새 Markdown parser 없이 기존 문자열 helper를 exact line match로 축소한다. PRD·제품·Playwright config·E2E spec은 변경하지 않는다.

  • 범위 밖: Markdown 전체 parser, 과거 review 이력 삭제, WebKit/mobile Safari 실행.

  • Phase 9 Gate 연결: synthetic negative-control, docs contract, 두 Playwright --list, typecheck·lint·link·diff 검사가 통과하기 전에는 완료하지 않는다.

  • RED: 목표 heading 뒤 임의 suffix를 붙인 가짜 H2가 선택되고 최신 Phase 9 H2가 검사되지 않는 synthetic 실패를 고정했다.

  • GREEN: helper를 전체 heading 행 exact match로 축소하고 contract 대상을 최신 Phase 9 H2와 그 내부 종료 판정으로 갱신했다.

  • REFACTOR: 날짜 suffix를 prefix 허용으로 숨기지 않고 호출부가 실제 전체 heading을 명시하게 하며 기존 H2/H3 경계 assertion을 유지했다.

  • focused·Playwright list·정적·link·diff 결과를 Progress와 Phase 9 리뷰에 누적한다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run typecheck; npm run lint; npm run e2e:mock -- --list; npm run e2e -- --list; node -e 'const fs=require("fs"),path=require("path");const root="docs/20260725_AI캐릭터관리자웹";const files=[];const walk=d=>{for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):e.name.endsWith(".md")&&files.push(p)}};walk(root);const broken=[];for(const f of files){const s=fs.readFileSync(f,"utf8");for(const m of s.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)){const t=m[1].trim().replace(/^<|>$/g,"").split("#")[0];if(t&&!/^(https?:|mailto:)/.test(t)&&!fs.existsSync(path.resolve(path.dirname(f),t)))broken.push(f+": "+t)}}if(broken.length){console.error(broken.join("\n"));process.exit(1)}'; git diff --check.

  • 기대 결과: docs contract 2 files / 9 tests, 임의 suffix·과거 H2·후속 H2/H3 false positive 0건, mock 104·server 18 tests가 chromium·mobile-chrome에서만 수집되고 정적·link·diff 오류 0건.

  • 수동 확인: Phase 9 최신 H2와 종료 판정만 읽어 현재 발견·소유 Task를 재구성할 수 있고 과거 완료 section의 문구가 현재 판정을 대신하지 않는지 확인한다.

P9-R16 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts — 1 file / 2 failed / 4 passed. 최신 Phase 9 종료 판정이 아직 후속 goal 필요라 실패했고, P10 current-state도 후속 Task 반영 전 상태라 함께 실패했다.
  • GREEN: sectionAtHeading()을 전체 heading 행 exact match로 축소하고 Phase 9 contract가 최신 ## 24. P9-R14~R15 수정 결과 재점검 — 2026-07-31와 내부 ### 종료 판정을 검사하게 했다. phase9-cross-cutting-quality.mdREV-P9-016 상태와 종료 판정을 수정 완료로 갱신했다.
  • focused: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts -t "keeps current Phase 9" — 1 file / 1 passed / 5 skipped.
  • 검증: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 9 tests passed. npm run typecheck, npm run lint, 필수 12 section 검사, Markdown link 검사, git diff --check — 모두 exit 0. npm run e2e:mock -- --list는 Chromium/mobile Chrome 104 tests, npm run e2e -- --list는 Chromium/mobile Chrome 18 tests를 수집했다.

Task R9.17 — Phase 9 current metadata·최신 결론 동기화

Goal 실행 P9-R17: P10-R14 완료 뒤에도 후속 필요로 남은 Phase 9 review metadata를 현재 상태로 맞추고 docs contract가 metadata와 실제 최신 H2·종료 판정을 함께 검사하게 한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-017

  • 시작 조건: Phase 9 review 상단 리뷰 상태와 최신 ## 24 종료 판정이 완료된 P10-R14를 후속 필요로 표시하지만 current docs contract가 metadata를 읽지 않고 과거 H2를 계속 대상으로 삼아 통과하는 근거.

  • 완료 증거: Phase 9 ## 1. 리뷰 정보, 실제 마지막 H2와 내부 ### 종료 판정의 동일 current-state assertion, P10-R14 후속 문구와 새 trailing H2 누락을 각각 차단하는 negative-control.

  • Files: Modify: src/shared/mocks/__tests__/mock-preview-docs.test.ts, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md.

  • Interfaces: 기존 exact heading helper를 재사용하고 과거 H2·판정 이력은 수정하지 않는다. 제품·PRD·Playwright 설정은 변경하지 않는다.

  • 범위 밖: 과거 review 결론 삭제, 제품/E2E 코드 변경, WebKit/mobile Safari 실행.

  • Phase 9 Gate 연결: docs contract, synthetic metadata/최신 H2 scope, 두 Playwright --list, typecheck·lint·link·diff 검사가 통과하기 전에는 완료하지 않는다.

  • RED: Phase 9 metadata와 실제 마지막 H2가 P10-R14 후속 필요를 유지하거나 새 trailing H2가 추가돼도 현재 contract가 과거 ## 24로 통과하는 실패를 고정했다.

  • GREEN: 현재 상태를 기록하는 새 H2를 append하고 metadata·문서의 마지막 H2·내부 종료 판정을 독립 추출해 완료 상태를 검사한다.

  • REFACTOR: 과거 ## 24 이력은 보존하고 current assertion이 이전 H2를 최신 판정으로 사용하지 못하게 했다.

  • focused·Playwright list·정적·link·diff 결과를 Progress와 Phase 9 리뷰에 누적한다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run typecheck; npm run lint; npm run e2e:mock -- --list; npm run e2e -- --list; node -e 'const fs=require("fs"),path=require("path");const root="docs/20260725_AI캐릭터관리자웹";const files=[];const walk=d=>{for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):e.name.endsWith(".md")&&files.push(p)}};walk(root);const broken=[];for(const f of files){const s=fs.readFileSync(f,"utf8");for(const m of s.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)){const t=m[1].trim().replace(/^<|>$/g,"").split("#")[0];if(t&&!/^(https?:|mailto:)/.test(t)&&!fs.existsSync(path.resolve(path.dirname(f),t)))broken.push(f+": "+t)}}if(broken.length){console.error(broken.join("\n"));process.exit(1)}'; git diff --check.

  • 기대 결과: docs contract 2 files / 9 tests, Phase 9 metadata·최신 H2·종료 판정 불일치 0건, mock 104·server 18 tests가 chromium·mobile-chrome에서만 수집되고 정적·link·diff 오류 0건.

  • 수동 확인: Phase 9 상단 metadata와 마지막 H2만 읽어 자동 보완 완료·Chrome 2종·남은 수동 QA를 같은 상태로 판단할 수 있는지 확인한다.

Task R9.18 — Phase 9 finding·checklist 상태 종결 contract

Goal 실행 P9-R18: P9-R17 완료 결론과 소유 finding REV-P9-017의 상태·Task checklist를 같은 수정 완료로 유지하고 이후 불일치를 자동으로 차단한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-018.

  • 시작 조건: 최신 ## 26과 metadata는 P9-R17 완료를 선언하지만 REV-P9-017 표는 확정 상태다. docs contract는 소유 finding 표를 검사하지 않고 P9-R17 범위를 P10-T1까지 열어 두어 후속 Phase 9 Task의 미완료 checklist까지 섞는다.

  • 완료 증거: REV-P9-017 표의 수정 완료 상태와 2026-08-01 수정 근거, P9-R17 자체 checklist 경계, Phase 9 metadata·최신 결론·소유 finding 상태를 함께 검사하는 assertion.

  • Files: Modify: src/shared/mocks/__tests__/mock-preview-docs.test.ts, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md.

  • Interfaces: Phase 9가 소유한 finding 상태만 검사한다. Phase 10의 현재 Task를 Phase 9 metadata에 중복 기록하지 않는다.

  • 범위 밖: 제품·Playwright 설정 변경, 과거 결론 삭제, WebKit/mobile Safari 실행.

  • RED: REV-P9-017 상태가 확정이어도 contract가 통과하고, 후속 Phase 9 Task를 append하면 P9-R17 checklist 범위가 함께 실패하는 두 경계를 고정한다.

  • GREEN: 소유 finding 표를 수정 완료로 갱신하고 날짜별 수정 근거를 append한 뒤 P9-R17 자체 checklist·metadata·최신 결론과 함께 검사한다.

  • REFACTOR: Phase 10 current-state 토큰을 Phase 9 metadata에서 제거하고 Phase 9 소유 상태만 남긴다.

  • docs contract·typecheck·lint·link·diff 결과를 Progress와 Phase 9 리뷰에 누적한다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run typecheck; npm run lint; Markdown link 검사; git diff --check.

  • 기대 결과: REV-P9-017 상태와 현재 결론 불일치 0건, docs contract·정적·link·diff 오류 0건.

P9-R18 수정 검증 기록 (2026-08-01):

  • RED: keeps current Phase 9REV-P9-017확정 상태로 1 failed / 5 skipped였고 checklist assertion 추가 뒤에는 후속 미완료 Task가 포함돼 1 failed / 7 skipped였다.
  • GREEN/REFACTOR: P9-R17 Task를 다음 H3에서 닫고 REV-P9-017·REV-P9-018 상태·날짜별 근거를 직접 검사했다. Phase 9 metadata에서 Phase 10 current Task 중복을 제거했다.

Task R9.19 — fenced code 내부 가짜 H2 배제

Goal 실행 P9-R19: 최신 H2 탐색이 fenced code block 안의 ## 예시나 앞선 동일 제목을 실제 마지막 review section으로 선택하지 않게 한다.

  • 연결 리뷰: Phase 9 현재 리뷰REV-P9-019.

  • 시작 조건: latestSectionAtLevel()이 Markdown fence 상태를 구분하지 않아 문서 최하단 code fence 안의 ## example을 최신 H2로 반환하고, 마지막 heading 문자열을 다시 첫 occurrence로 탐색해 동일 제목이 반복되면 앞선 section을 반환하는 synthetic 재현 근거.

  • 선행조건: P9-R18 완료.

  • 완료 증거: backtick·tilde fence 내부 heading을 제외하고 실제 마지막 H2의 index를 직접 사용하는 최소 helper와 fence·동일 제목 synthetic assertion.

  • Files: Modify: src/shared/mocks/__tests__/mock-preview-docs.test.ts, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase9-cross-cutting-quality.md.

  • Interfaces: 새 Markdown parser나 dependency 없이 기존 helper에 fence 상태만 추가한다.

  • 범위 밖: Markdown 전체 문법 지원, 제품·Playwright 설정 변경, WebKit/mobile Safari 실행.

  • RED: 실제 H2 뒤 fenced code 내부 가짜 H2 또는 같은 제목의 앞선 H2가 있으면 실제 마지막 section을 놓치는 실패를 고정한다.

  • GREEN: backtick·tilde fence 구간을 건너뛰고 탐색한 index에서 실제 마지막 H2 section을 직접 추출한다.

  • REFACTOR: fence 처리 외 heading/section 경계 로직은 기존 helper를 재사용한다.

  • docs contract·typecheck·lint·link·diff 결과를 Progress와 Phase 9 리뷰에 누적한다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run typecheck; npm run lint; Markdown link 검사; git diff --check.

  • 기대 결과: fenced heading·중복 제목 false positive 0건, docs contract·정적·link·diff 오류 0건.

P9-R19 수정 검증 기록 (2026-08-01):

  • RED: backtick·tilde fence 내부 H2와 동일 제목 H2 synthetic 2건이 2 failed / 6 skipped로 재현됐다.
  • GREEN/REFACTOR: fence 밖 heading index를 한 번 수집해 실제 마지막 H2를 직접 추출했다. synthetic 2 passed / 6 skipped, Phase 9 current-state 포함 focused 3 passed / 5 skipped, docs contract 2 files / 12 tests passed였다.

Phase 10. OpenAPI 2.3.0 후속 계약 vertical slices

목표: OpenAPI 2.3.0에서 해결된 lookup·댓글·pagination 계약과 UTC 전송 변경, FanTalk 수정·삭제, 가격·공통 오류·파일 정책을 기존 UI에 반영하고 mock/client 완료와 실제 server integration 상태를 분리해 검증한다.

Phase Goal P10: Task 10.1 → 10.7과 Phase 10 Gate로 후속 계약을 도메인별 독립 test cycle로 구현한다.

  • 시작 조건: 2026-07-28 Phase 3~9 기록 보존, PRD의 2026-07-29 결정 기록과 OpenAPI 2.3.0 확인.
  • 완료 조건: P10-T1~P10-T7, P10-GATE 완료.
  • 실행 순서: Character lookup → Audio UTC → Series genre/CRUD → Community pagination → FanTalk 수정·삭제 → Comments → 가격·오류·파일 정책 → 통합 Gate.
  • 요구사항: CHAR-012~014, AUDIO-003, AUDIO-009~011, AUDIO-019, AUDIO-022, AUDIO-025, AUDIO-030, SERIES-001~018, COMMUNITY-007~008, COMMUNITY-010, COMMUNITY-012, FANTALK-003~012, COMMENT-001~008, FILE-001~015, EXT-001~005, EXT-007~011.
  • 범위 밖: 제품 범위에서 제외한 FanTalk 별도 상세 GET, 답변 상태 filter, sort, 중복 생성의 정확한 도메인 오류 key와 답변 삭제, 인증 operation 정식화.
  • server 검증 전제: series.spec.ts, fan-talk.spec.ts, comments.spec.ts를 server mode testMatch에 포함해 file filter와 교집합이 생기게 한다. 실제 mutation 검증은 Chromium 1개 project와 실행 환경이 제공한 전용 개발 API 계정·재사용 가능한 fixture ID로 한정해 같은 destructive mutation의 browser별 반복을 피한다. 계정·fixture가 없으면 client/mock GREEN은 진행하되 해당 server 명령을 No tests found나 mock 성공으로 대체하지 않고 server integration 대기로 기록한다.
Goal 선행조건 독립 결과
P10-T1 OpenAPI 2.3.0 원작 lookup implemented Character v2 원작 검색·선택
P10-T2 Audio UTC schema와 no-timezone 계약 Audio UTC 생성·조회 migration
P10-T3 Series 장르·CRUD implemented Series 장르 선택·CRUD form
P10-T4 Community pagination schema implemented 서버 metadata 기반 목록
P10-T5 FanTalk DELETE·PUT implemented 팬 원글 삭제와 reply 수정 client/mock/server
P10-T6 Audio·Community 댓글 operation implemented 두 target의 2단계 댓글 관리
P10-T7 EXT-009~011 해결과 기존 파일 client 검증 완료 가격 상한·공통 오류 fallback·파일 server 경계
P10-GATE P10-T1~P10-T7 완료 후속 계약 통합 검증·server 상태 분리

Task 10.1 Character v2 원작 검색

Goal 실행 P10-T1: legacy 원작 lookup 전제를 제거하고 v2 OriginalWorkSearchItem 기반 선택기와 create/update payload를 완성한다.

  • 시작 조건: OpenAPI searchAiCharacterOriginalWorksimplemented.

  • Files: Modify: src/features/characters/api/character-api.ts, src/features/characters/model/types.ts, src/features/characters/pages/CharacterCreatePage.tsx, src/features/characters/pages/CharacterEditPage.tsx, src/features/characters/tests/character-api.test.ts, src/features/characters/tests/CharacterCreatePage.test.tsx, src/features/characters/tests/CharacterEditPage.test.tsx, src/shared/mocks/character-fixtures.ts, src/shared/mocks/character-mock-store.ts, src/shared/mocks/handlers.ts, src/shared/mocks/__tests__/character-handlers.test.ts, tests/e2e/character-workspace.spec.ts.

  • Interfaces: searchOriginalWorks(apiClient, searchTerm): Promise<OriginalWorkSearchItem[]>; OriginalWorkSearchItem={id,title,contentType,category,isAdult,description,originalWork,originalLink,writer,studio,originalLinks,tags,imageUrl}; create/update의 선택값은 originalWorkId, 미선택은 key 생략.

  • 범위 밖: legacy /admin/chat/original/search, 원작 생성·수정 UI.

  • RED: v2 path와 필수 searchTerm, 전체 DTO parse, 선택/해제 payload를 검증하는 실패 test를 먼저 작성한다.

  • GREEN: 300ms 부분 검색 선택기를 생성·수정 화면에 연결하고 선택한 idoriginalWorkId로 직렬화한다.

  • REFACTOR: legacy lookup fixture·path 문자열을 제거하고 Character focused test와 mock E2E를 재실행한다.

  • 실행 명령: npm run test:run -- src/features/characters src/shared/mocks/__tests__/character-handlers.test.ts; npm run e2e:mock -- tests/e2e/character-workspace.spec.ts.

  • 기대 결과: focused/unit와 mock E2E exit 0, v2 원작 검색 호출 1건 이상, legacy lookup 호출 0건, 미선택 payload의 originalWorkId 0건.

  • 수동 확인: desktop/tablet에서 원작 검색→선택→저장과 선택 해제를 확인한다. 모바일 Character mutation은 기존 미지원 정책을 유지한다.

Task 10.2 Audio UTC 예약 공개 계약 전환

Goal 실행 P10-T2: 예약 입력을 client에서 UTC Z로 변환하고 Audio 생성·상세의 모든 timezone field/query를 제거한다.

  • 시작 조건: OpenAPI AudioContentCreateRequest.releaseDate가 nullable date-time, pattern=Z$이고 timezone schema/parameter가 없음.

  • Files: Modify: src/features/audio-contents/api/audio-content-api.ts, src/features/audio-contents/components/audio-content-form-helpers.ts, src/features/audio-contents/model/types.ts, src/features/audio-contents/schemas/audio-content-schema.ts, src/features/audio-contents/pages/AudioContentDetailPage.tsx, src/features/audio-contents/pages/AudioContentFormPage.tsx, src/features/audio-contents/tests/audio-contract.test.ts, src/features/audio-contents/tests/audio-form.test.tsx, src/features/audio-contents/tests/audio-form-test-support.tsx, src/features/audio-contents/tests/audio-list.test.tsx, src/shared/mocks/audio-content-fixtures.ts, src/shared/mocks/audio-content-mock-store.ts, src/shared/mocks/handlers.ts, src/shared/mocks/__tests__/character-handlers.test.ts, tests/e2e/audio-content.spec.ts.

  • Interfaces: toUtcReleaseDate(value, "Asia/Seoul"): string; toCreateRequest(...).releaseDate: string|null; getAudioContent(apiClient,{characterId,contentId}); request type에는 timezone이 없음.

  • 범위 밖: 수정 화면의 schedule 변경, status enum/filter.

  • RED: 2026-07-29T18:00 Asia/Seoul이 2026-07-29T09:00:00Z로 직렬화되고 즉시 공개는 null, timezone key/query는 0건인 실패 test를 작성한다.

  • GREEN: 예약 값을 UTC ISO-8601 Z로 변환하고 create serializer·detail adapter·mock handler에서 timezone을 제거한다.

  • REFACTOR: 목록·상세 fixture의 non-UTC releaseDateZ 값으로 통일하고 Asia/Seoul 표시 formatter 회귀를 확인한다.

  • 실행 명령: npm run test:run -- src/features/audio-contents src/shared/mocks/__tests__/character-handlers.test.ts; npm run e2e:mock -- tests/e2e/audio-content.spec.ts.

  • 기대 결과: focused/unit와 mock E2E exit 0, 예약 request releaseDateZ$와 일치, timezone request key/query 0건, 즉시 공개 releaseDate=null.

  • 수동 확인: Asia/Seoul 예약 입력과 화면 표시가 유지되면서 network multipart의 JSON part만 UTC로 전송되는지 확인한다.

Task 10.3 Series v2 장르·CRUD form

Goal 실행 P10-T3: 활성 장르 목록과 SeriesListItem 상세 원본값으로 Series 생성·수정·soft delete 흐름을 완료한다.

  • 시작 조건: listAiCharacterSeriesGenres, Series CRUD가 implemented; 상세 성공 dataSeriesListItem.

  • Files: Modify: src/features/series/api/series-api.ts, src/features/series/model/types.ts, src/features/series/pages/SeriesDetailPage.tsx, src/features/series/tests/series-contract.test.ts, src/shared/mocks/series-fixtures.ts, src/shared/mocks/series-handlers.ts, src/shared/mocks/series-mock-store.ts, src/shared/mocks/handlers.ts, tests/e2e/series.spec.ts, playwright.config.ts, src/app/route-paths.ts, src/app/App.tsx; Create: src/features/series/schemas/series-schema.ts, src/features/series/validation/series-image-policy.ts, src/features/series/pages/SeriesFormPage.tsx, src/features/series/components/SeriesForm.tsx, src/features/series/components/PublishedDaysField.tsx, src/features/series/components/GenreCombobox.tsx, src/features/series/tests/series-form.test.tsx.

  • Interfaces: getSeriesGenres(apiClient): Promise<SeriesGenreItem[]>; SeriesGenreItem={id,genre,isAdult}; getSeriesDetail(...): Promise<SeriesListItem>; SeriesCreateRequest={title:string,introduction:string,publishedDaysOfWeek:SeriesPublishedDay[],keyword:string,genreId?:number,isAdult?:boolean,writer?:string|null,studio?:string|null}; SeriesUpdateRequest={title?:string|null,introduction?:string|null,publishedDaysOfWeek?:SeriesPublishedDay[]|null,genreId?:number|null,isAdult?:boolean|null,state?:SeriesState|null,isActive?:boolean|null,writer?:string|null,studio?:string|null}. seriesCreateFormSchema는 image·title·introduction·요일·keyword·유효한 genreId를 필수로 하고 state를 갖지 않으며, seriesUpdateFormSchema는 image와 state를 optional로 하고 keyword를 갖지 않는다. SERIES_IMAGE_POLICY=createImagePolicy({aspect:210/297,cropRequired:true,maxWidth:1000,noUpscale:true}); validateSeriesImageFile(file).jpg/.jpeg + image/jpeg, .png + image/png, 최대 10_485_760 bytes만 허용한다. createSeries(apiClient,{characterId,image,request}): Promise<null>은 필수 image와 request를, updateSeries(apiClient,{characterId,seriesId,image?,request}): Promise<null>은 optional 교체 image와 request를 보내고, deactivateSeries는 update request의 isActive=false만 사용한다.

  • 범위 밖: create-only keyword의 수정·상세 표시, 장르 검색 query, 모바일 Series mutation.

  • RED: query/body 없는 장르 GET, idgenreId, 상세 enum 초기화, 생성 필수 image·keyword/no state, 수정 optional image·no keyword, soft delete isActive=false, RANDOM 단독 선택의 실패 test를 작성한다. Series image는 JPEG/PNG·10_485_760 bytes, 210:297, 최대 폭 1,000px, no-upscale과 round(width × 297 ÷ 210) 세로값 1px 이내를 검증한다.

  • GREEN: desktop/tablet form과 v2 장르 선택기, FileField·ImageCropDialog 기반 Series image 선택/crop을 구현하고 생성·수정·soft delete 후 목록 재조회·이동·알림을 연결한다. server mode testMatchseries.spec.ts를 추가한다.

  • REFACTOR: 표시용 문자열 상세 fixture와 legacy 장르 lookup 전제를 제거하고 enum label 변환을 표시 계층에만 둔다. create/update serializer와 image policy를 form에서 분리하고 mock 전용 ID·로그인 helper와 server fixture path를 분리해 focused test와 mock/server E2E를 재실행한다.

  • 실행 명령: npm run test:run -- src/features/series; npm run e2e:mock -- tests/e2e/series.spec.ts; npm run e2e -- tests/e2e/series.spec.ts --project=chromium.

  • 기대 결과: focused/unit와 mock E2E exit 0, legacy 장르 호출 0건, genreId=0·생성 state·수정 keyword payload 0건, 허용 범위 밖 image request 0건이다. server 명령은 No tests found가 아니고 Series spec을 실제 실행하며, server 완료 판정 시 필수 개발 fixture path가 skipped 0건이다.

  • 수동 확인: desktop/tablet에서 장르 선택→image crop→생성→직접 edit 초기화→optional image 교체 수정→soft delete를 확인하고 모바일에는 mutation action이 없음을 확인한다. 실제 개발 API 결과는 mock 결과와 별도로 기록한다.

Task 10.4 Community pagination·timezone 제거

Goal 실행 P10-T4: Community 목록을 OpenAPI pagination object로 전환하고 timezone query 없이 서버 metadata로 page UI를 제어한다.

  • 시작 조건: CommunityPostListResponse={totalCount,page,size,hasNext,items}implemented.

  • Files: Modify: src/features/community-posts/api/community-post-api.ts, src/features/community-posts/model/types.ts, src/features/community-posts/pages/CommunityPostListPage.tsx, src/features/community-posts/components/CommunityPostList.tsx, src/features/community-posts/tests/community-contract.test.ts, src/features/community-posts/tests/community-list.test.tsx, src/features/community-posts/tests/community-sheet.test.tsx, src/features/community-posts/tests/community-test-support.ts, src/shared/mocks/community-post-fixtures.ts, src/shared/mocks/community-post-handlers.ts, src/shared/mocks/community-post-mock-store.ts, tests/e2e/community.spec.ts.

  • Interfaces: getCommunityPosts(apiClient,{characterId,page,size}): Promise<PageData<CommunityPostListItem>>; page state는 response의 totalCount, page, size, hasNext, items를 그대로 사용.

  • 범위 밖: Community 검색, detail GET/route.

  • RED: timezone 없는 query와 pagination object parse, hasNext 기반 다음 page, mutation 후 현재 page 재조회의 실패 test를 작성한다.

  • GREEN: adapter·schema·mock store·목록 UI를 서버 metadata 기반으로 갱신한다.

  • REFACTOR: 배열 response fallback과 client 추정 종료 규칙을 제거하고 기존 Sheet/cache 흐름 회귀를 확인한다.

  • 실행 명령: npm run test:run -- src/features/community-posts; npm run e2e:mock -- tests/e2e/community.spec.ts.

  • 기대 결과: focused/unit와 mock E2E exit 0, timezone query 0건, 응답 metadata와 화면 page/전체 건수 차이 0건.

  • 수동 확인: desktop/tablet/mobile 목록에서 다음/이전 page와 전체 건수가 서버 값대로 표시되고 Sheet mutation 뒤 현재 page가 유지되는지 확인한다.

Task 10.5 FanTalk 답변 수정·팬 원글 soft delete

Goal 실행 P10-T5: creatorReplies 상태를 기준으로 작성/수정을 분기하고 팬 작성 root soft delete를 모든 viewport에 제공한다.

  • 시작 조건: FanTalk DELETE와 reply PUT이 모두 implemented.

  • Files: Modify: src/features/fan-talks/api/fan-talk-api.ts, src/features/fan-talks/model/types.ts, src/features/fan-talks/schemas/fan-talk-reply-schema.ts, src/features/fan-talks/pages/FanTalkListPage.tsx, src/features/fan-talks/components/FanTalkList.tsx, src/features/fan-talks/components/FanTalkListItem.tsx, src/features/fan-talks/components/FanTalkReplySheet.tsx, src/features/fan-talks/components/FanTalkReplyForm.tsx, src/features/fan-talks/tests/fan-talk-contract.test.ts, src/features/fan-talks/tests/fan-talk-list.test.tsx, src/features/fan-talks/tests/fan-talk-reply.test.tsx, src/shared/mocks/fan-talk-fixtures.ts, src/shared/mocks/fan-talk-handlers.ts, src/shared/mocks/fan-talk-mock-store.ts, tests/e2e/fan-talk.spec.ts, playwright.config.ts.

  • Interfaces: updateFanTalkReply(apiClient,{characterId,fanTalkId,replyId,request:{content}}): Promise<FanTalkListItem>에서 replyId=creatorReplies[0].fanTalkId; PUT 성공 data.fanTalkId는 수정된 reply row ID이므로 root item ID로 사용하지 않고 현재 목록을 재조회한다. deleteFanTalk(apiClient,{characterId,fanTalkId}): Promise<null>.

  • 범위 밖: 답변 삭제·재활성화, PUT의 isActive, 제품 범위에서 제외한 별도 상세/filter/sort와 중복 오류 key.

  • RED: 빈 배열은 POST, non-empty는 PUT, PUT replyId mapping과 {content} 전용 request, PUT 성공 response parse 후 목록 재조회, 팬 원글 DELETE body 없음, 한 화면의 중복 submit 차단과 일반 오류 후 재조회의 실패 test를 작성한다. server path에서는 동일 root에 동시 POST 후 재조회한 creatorReplies의 활성 답변이 하나인지 검증하되 정확한 오류 status/message key를 assertion하지 않는다.

  • GREEN: 답변 수정 form과 삭제 확인 Dialog·API·mock store를 구현하고 server mode testMatchfan-talk.spec.ts를 추가한다.

  • REFACTOR: 첫 답변만 수정 대상으로 사용하는 helper를 한곳에 두고 작성·수정·삭제의 pending/error/live feedback을 공통화한다. mock 전용 ID·로그인 helper와 server fixture path를 분리하고 별도 상세/filter/sort·중복 오류 key용 adapter·fixture가 생기지 않았는지 확인한다.

  • 실행 명령: npm run test:run -- src/features/fan-talks; npm run e2e:mock -- tests/e2e/fan-talk.spec.ts; npm run e2e -- tests/e2e/fan-talk.spec.ts --project=chromium.

  • 기대 결과: focused/unit와 mock E2E exit 0, 답변 완료 item의 두 번째 POST 0건, PUT path의 replyIdcreatorReplies[0].fanTalkId, PUT request의 isActive 0건, DELETE body 0 bytes다. server 명령은 No tests found가 아니고 FanTalk spec을 실제 실행하며, server 완료 판정 시 전용 fixture path가 skipped 0건이고 동시 POST 후 활성 creator reply는 1개다.

  • 수동 확인: desktop/tablet/mobile에서 답변 작성·수정과 팬 원글 삭제, focus 복귀를 확인한다. 실제 server mode의 수정·재조회와 단일 답변 결과를 mock 증거와 별도로 기록하고 exact 충돌 key는 수집해도 client 분기에 사용하지 않는다.

Task 10.6 Audio·Community 댓글 2단계 관리

Goal 실행 P10-T6: 두 target의 댓글·직접 답글 조회, AI 명의 작성, AI 작성분 수정, 작성자 무관 soft delete를 모든 viewport에 제공한다.

  • 시작 조건: Audio·Community comment 10개 operation과 관련 schema가 implemented.

  • Files: Create: src/features/comments/api/comment-api.ts, src/features/comments/model/types.ts, src/features/comments/components/CommentThread.tsx, src/features/comments/components/CommentForm.tsx, src/features/comments/components/CommentItem.tsx, src/features/comments/tests/comment-contract.test.ts, src/features/comments/tests/comment-thread.test.tsx, src/shared/mocks/comment-fixtures.ts, src/shared/mocks/comment-handlers.ts, src/shared/mocks/comment-mock-store.ts, tests/e2e/comments.spec.ts; Modify: src/features/audio-contents/pages/AudioContentDetailPage.tsx, src/features/audio-contents/model/types.ts, src/features/community-posts/components/CommunityPostSheet.tsx, src/features/community-posts/model/types.ts, src/shared/mocks/handlers.ts, playwright.config.ts.

  • Interfaces: CommentTarget={kind:"audio",characterId,contentId,creatorId}|{kind:"community",characterId,postId,creatorId}; CommentRecord={id,writerId,nickname,profileUrl,comment,isSecret,date,replyCount,languageCode?:string|null,donationCan?:number}; getRootComments(target,{page,size})getReplies(target,{commentId,page,size}): Promise<{totalCount:number,items:CommentRecord[]}>; Audio create request는 {comment,parentId?:number|null,isSecret:boolean,languageCode?:string|null}, Community create request는 {comment,parentId?:number|null,isSecret:boolean}, update request는 {comment:string}, createComment/updateComment/deleteComment: Promise<null>; canEdit = comment.writerId === target.creatorId.

  • 범위 밖: 3단계 답글, 팬 댓글 수정, delete cascade, comment status/filter.

  • RED: target별 path/schema, root/replies GET의 page·sizetotalCount parse, root/parentId, target별 create request 차이, writerId===creatorId 수정 권한, 작성자 무관 DELETE, UTC 표시의 실패 test를 작성한다.

  • GREEN: target adapter와 2단계 thread UI·mock handler/store를 구현하고 Audio 상세·Community Sheet에 연결한다. mock·server mode testMatchcomments.spec.ts를 추가한다.

  • REFACTOR: target별 DTO 차이는 adapter에서 정규화하고 Page/Sheet는 target 식별자와 creatorId만 전달하도록 정리한다. mock 전용 ID·로그인 helper가 server path에서 사용되지 않도록 분리한다.

  • 실행 명령: npm run test:run -- src/features/comments src/features/audio-contents src/features/community-posts; npm run e2e:mock -- tests/e2e/comments.spec.ts; npm run e2e -- tests/e2e/comments.spec.ts --project=chromium.

  • 기대 결과: focused/unit와 지원 project mock E2E exit 0, 3단계 작성 UI/request 0건, 팬 댓글 PUT 0건, 팬·AI 댓글 DELETE 모두 body 없음이다. server 명령은 No tests found가 아니고 Comments spec을 실제 실행하며, server 완료 판정 시 Audio·Community 전용 fixture path가 skipped 0건이다.

  • 수동 확인: Audio 상세와 Community Sheet에서 root/답글 작성·AI 작성분 수정·팬 댓글 삭제를 keyboard-only와 320px에서 확인한다. 같은 흐름의 실제 개발 API 결과를 mock 증거와 별도로 기록한다.

Task 10.7 가격·공통 오류·파일 정책 종결

Goal 실행 P10-T7: EXT-009~011을 공통·도메인 schema와 오류 처리, 기존 파일 검증 회귀와 server 확인 기준에 반영한다.

  • 시작 조건: EXT-0090..99999, EXT-010의 client/backend 검증 경계, EXT-011의 공통 오류 envelope와 미정의 오류 문구가 확정됨.

  • Files: Create: src/shared/validation/can-price.ts, src/shared/validation/can-price.test.ts; Modify: src/features/audio-contents/schemas/audio-content-schema.ts, src/features/audio-contents/components/AudioContentForm.tsx, src/features/audio-contents/api/upload-audio-content.ts, src/features/audio-contents/tests/audio-form.test.tsx, src/features/audio-contents/tests/audio-contract.test.ts, src/features/audio-contents/tests/audio-upload.test.ts, src/features/community-posts/model/types.ts, src/features/community-posts/components/CommunityPostForm.tsx, src/features/community-posts/tests/community-contract.test.ts, src/features/community-posts/tests/community-form.test.tsx, src/shared/api/api-error.ts, src/shared/api/client.ts, src/shared/api/__tests__/client.test.ts, src/shared/validation/file-media-policy.test.ts, tests/e2e/error-mapping.spec.ts.

  • Interfaces: CAN_PRICE_MAX=99_999; canPriceSchema0..99999 정수; UNKNOWN_API_ERROR_MESSAGE="알 수 없는 오류가 발생했습니다.". 유효한 ApiErrorResponse의 비어 있지 않은 server message, status, errorProperty는 보존하고 network·JSON parse·envelope 불일치·빈 message만 공통 문구의 ApiError로 정규화한다.

  • 범위 밖: 도메인별 미제공 message key 추정, client container/codec 판정, image 비율·crop의 backend 재검증.

  • RED: Audio create/update와 Community create schema·form에서 0, 99999 허용, -1, 100000, 소수 거부 실패 test를 먼저 작성한다.

  • RED: fetch와 XHR upload의 non-empty server message 보존, network·malformed/empty error의 공통 문구 변환 실패 test를 작성한다.

  • GREEN: 공통 가격 schema를 두 도메인 request/form에 적용하고 공통 fetch·XHR 오류 정규화를 최소 구현한다.

  • REFACTOR: 기존 image/audio 크기·MIME·GIF·no-upscale 경계 test를 재사용해 FILE-001~015 회귀를 확인하고 도메인별 오류 key 분기를 추가하지 않는다.

  • 실행 명령: npm run test:run -- src/shared/validation/can-price.test.ts src/shared/validation/file-media-policy.test.ts src/shared/api/__tests__/client.test.ts src/features/audio-contents src/features/community-posts; npm run e2e:mock -- tests/e2e/error-mapping.spec.ts.

  • 기대 결과: 모든 명령 exit 0, 100000 이상 또는 음수·소수 price request 0건, 미정의 오류 문구 불일치 0건, 기존 file boundary 회귀 0건.

  • 수동 확인: Audio 생성·수정과 Community 생성에서 가격 경계·오류 focus를 확인한다. 실제 개발 API 통합 확인에서는 backend가 검증 가능한 image/audio 용량·MIME 위반 multipart가 성공 처리되지 않고, 정확한 미제공 status/message key에 따른 client 분기가 생기지 않았는지 확인한다.

Task R10.1 — 공통 오류 fallback migration 종결

Goal 실행 P10-R1: fetch/XHR의 미정의 오류를 공통 문구로 통일하고 현재 전체 unit Gate의 6개 stale assertion을 복구한다.

  • 연결 리뷰: Phase 10 재리뷰REV-P10-001

  • 시작 조건: EXT-011, UNKNOWN_API_ERROR_MESSAGE; non-empty server message 보존.

  • Files: Modify: src/features/audio-contents/api/upload-audio-content.ts, src/features/audio-contents/tests/audio-upload.test.ts, src/features/auth/tests/auth-api.test.ts, src/app/App.protected-errors.test.tsx, vite.config.ts, 필요 시 src/shared/api tests; Test: 동일 경로와 tests/e2e/error-mapping.spec.ts.

  • Interfaces: network·JSON parse·envelope mismatch·empty message → 알 수 없는 오류가 발생했습니다.; valid non-empty message/status/errorProperty 보존.

  • 범위 밖: 도메인별 미제공 message key, 401 session 처리(P1-R6/P4-R2).

  • RED: XHR 2xx malformed JSON/envelope가 과거 문구를 반환하는 실패 test와 전체 unit 6개 stale assertion을 현재 정책 기준으로 고정한다.

  • GREEN: XHR success parse fallback을 공통 상수로 변경하고 App/auth assertion을 최신 정책으로 정렬한다.

  • REFACTOR: fetch/XHR 오류 matrix 이름과 fixture를 같은 용어로 정리하고 full unit/error E2E를 실행한다.

  • 실행 명령: npm run test:run -- src/shared/api src/features/auth src/features/audio-contents/tests/audio-upload.test.ts src/app/App.protected-errors.test.tsx; npm run test:run; npm run e2e:mock -- tests/e2e/error-mapping.spec.ts.

  • 기대 결과: exit 0, 전체 unit 0 failure, 미정의 오류 문구 불일치 0건.

  • 수동 확인: 보호 route와 upload에서 malformed response의 한국어 공통 안내를 확인한다.

Task R10.2 — PRD OpenAPI 2.3.0 현재 상태 정합성 복구

Goal 실행 P10-R2: PRD의 현재형 API metadata와 Community pagination 설명을 OpenAPI 2.3.0 실제 집계와 일치시킨다.

  • 연결 리뷰: Phase 10 재리뷰REV-P10-002

  • 시작 조건: OpenAPI actual 3.1.0, document 2.3.0, 25 paths, 37 operations, Community pagination metadata 제공.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/review-phase-10-20260729.md; Test: 없음.

  • Interfaces: 현재 상태 문장만 정정하며 2.0.0 당시 Decision/Progress 기록은 보존한다.

  • 범위 밖: OpenAPI JSON 변경, 과거 검증 수치 재작성.

  • TDD 예외: 문서 정합성 Task이므로 jq actual 집계와 stale 현재형 문구 검색을 RED 대체 검증으로 기록한다.

  • 현재 계약 version/path/operation, 최종 수정일과 Community metadata 제공 상태를 정정한다.

  • 과거 2.0.0 이력 보존과 현재형 stale 문구 0건, Markdown link·diff를 검증한다.

  • 실행 명령: OpenAPI jq 집계; rg -n 'version .2\\.0\\.0|15개 path|23개 operation|종료 metadata 계약이 제공되기 전' docs/20260725_AI캐릭터관리자웹/prd.md; git diff --check -- docs/20260725_AI캐릭터관리자웹.

  • 기대 결과: current-state stale 검색 exit 1, actual 집계 2.3.0/25/37, diff check exit 0.

  • 수동 확인: PRD 현재 상태와 Decision Log 과거 이력이 구분되는지 확인한다.

Task R10.3 — 계획·과거 Phase 10 리뷰 현재 상태 동기화

Goal 실행 P10-R3: plan 상단·구현 완료 정의와 과거 Phase 10 리뷰의 결론을 최신 Gate 정책 및 신규 리뷰 결과와 일치시킨다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-003

  • 시작 조건: 2026-07-30 P10-GATE 자동 범위 정정 및 수동 QA 전환 기록과 이번 리뷰의 신규 회귀 Task 상태.

  • 완료 증거: stale 현재 문구 검색, 현재 상태 갱신, 과거 기록 보존, Markdown link/diff 검증 통과.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/review-phase-10-20260729.md; Test: 문서 검색.

  • Interfaces: 자동 Gate와 수동 QA 대기를 구분하고, 신규 회귀 Task가 남아 있는 동안 최종 완료를 주장하지 않는다.

  • 범위 밖: 과거 server 실패 기록 삭제, 수동 QA를 자동 완료로 변경, 애플리케이션 코드 변경.

  • TDD 예외: 상단 상태, §6 설명, 과거 Phase 10 리뷰 결론의 stale 현재 문구를 검색해 RED 대체 증거로 기록한다.

  • 상단 상태와 §6 설명을 신규 Task 완료·자동 Gate/수동 QA 구분에 맞게 갱신하고 기존 날짜별 검증 기록은 보존한다.

  • 과거 Phase 10 리뷰에 P10-R1~R2 수정 완료와 Gate 정책 정정을 누적하고 새 리뷰 링크·diff를 검증한다.

  • 실행 명령: rg -n '2026-07-29 Phase별 재''리뷰|server E2E가 로그인 fixture에 막''혀|P10-GATE.*완료 아''님' docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-10-20260729.md; git diff --check -- docs/20260725_AI캐릭터관리자웹.

  • 기대 결과: 과거 사실 보존, 현재 Gate·수동 QA·신규 회귀 Task 상태 불일치 0건.

  • 수동 확인: plan 상단, §6, Phase 10 Gate, Decision Log, 과거/현재 리뷰 결론이 같은 현재 상태를 설명하는지 확인한다.

Progress — 2026-07-30 P10-R3 완료

  • RED 대체: stale 현재 상태 검색이 plan 상단의 2026-07-29 회귀 대기 문구, §6의 server E2E 차단 문구, 과거 Phase 10 리뷰의 Gate 미완료 결론을 검출했다.
  • GREEN: plan 상단과 §6을 자동 Gate 정리 완료·실제 개발 API 수동 QA 대기로 갱신하고, review-phase-10-20260729.mdP10-R1~R2와 Gate 정책 정정 검증을 누적했다.
  • REFACTOR: phase10-openapi-follow-up.mdREV-P10-003 상태·최종 결론도 수정 완료로 정렬했다. 2026-07-29 당시 실패·timeout 기록은 삭제하지 않았다.

Task R10.4 — FanTalk PUT 응답·생성 reply ID 계약 복구

Goal 실행 P10-R4: FanTalk reply PUT의 레거시 응답 shape를 별도 schema로 소비하고 POST 뒤 목록의 creatorReplies[].fanTalkId를 실제 replyId로 유지한다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-004, REV-P10-005

  • 시작 조건: OpenAPI FanTalkReplyUpdateApiResponse.data=FanTalkListItem, 해당 fanTalkId는 수정 reply row ID라는 설명, FANTALK-004, FANTALK-009~010.

  • 완료 증거: 실제 PUT response shape parse 실패와 POST→목록→PUT ID 실패 test, client/mock 최소 수정, FanTalk focused unit·mock E2E·정적 Gate 통과.

  • Files: Modify: src/features/fan-talks/api/fan-talk-api.ts, src/features/fan-talks/model/types.ts, src/features/fan-talks/schemas/fan-talk-reply-schema.ts, src/features/fan-talks/tests/fan-talk-contract.test.ts, src/features/fan-talks/tests/fan-talk-reply.test.tsx, src/shared/mocks/fan-talk-mock-store.ts, src/shared/mocks/fan-talk-handlers.ts, tests/e2e/fan-talk.spec.ts.

  • Interfaces: POST 성공은 FanTalkReplyResponse.replyId, PUT 성공은 FanTalkListItem shape로 분리한다. 목록 creatorReplies[].fanTalkId와 PUT path replyId는 POST가 반환한 reply row ID를 사용한다.

  • 범위 밖: FanTalk 답변 삭제, 별도 상세/filter/sort, PUT의 isActive UI, backend 응답 변경.

  • RED: OpenAPI PUT data shape가 현재 POST schema에서 거부되는 contract test와 POST replyId=9001 뒤 재조회·수정이 /replies/9001을 사용해야 하는 mock E2E를 작성한다.

  • GREEN: PUT 전용 response schema/type을 추가하고 mock create list mapping과 update response를 실제 계약에 맞춘다.

  • REFACTOR: POST/PUT request는 공통 {content}를 유지하되 서로 다른 response type을 이름과 fixture에서 명확히 분리한다.

  • 실행 명령: npm run test:run -- src/features/fan-talks src/shared/mocks; npm run e2e:mock -- tests/e2e/fan-talk.spec.ts --project=chromium; npm run typecheck; npm run lint; npm run build.

  • 기대 결과: 모든 명령 exit 0, OpenAPI PUT data parse 성공, POST→동일 item PUT path의 reply ID 일치, 성공한 PUT의 client parse 오류 0건.

  • 수동 확인: 실제 개발 API에서 기존 답변 PUT과 새 답변 POST 후 재진입 PUT을 각각 실행해 성공 안내·목록 refetch·path ID를 확인한다.

P10-R4 수정 검증 기록 (2026-07-30):

  • RED: npm run test:run -- src/features/fan-talks/tests/fan-talk-contract.test.ts는 OpenAPI PUT data shape가 기존 POST response schema에서 거부되어 실패했고, npm run e2e:mock -- tests/e2e/fan-talk.spec.ts --project=chromium은 POST 뒤 같은 item 수정 path가 /replies/9001이 아니어서 실패했다.
  • GREEN: PUT 전용 FanTalkReplyUpdateResponse schema/type을 FanTalkListItem 기반으로 추가하고, mock store가 POST replyId를 목록 creatorReplies[].fanTalkId에 보존하며 PUT 응답도 OpenAPI shape로 반환하게 했다.
  • REFACTOR/회귀: npm run test:run -- src/features/fan-talks/tests/fan-talk-contract.test.ts src/features/fan-talks/tests/fan-talk-reply.test.tsx는 2 files / 10 tests passed, npm run e2e:mock -- tests/e2e/fan-talk.spec.ts --project=chromium은 7 passed였다. npm run test:run -- src/features/fan-talks src/shared/mocks는 재실행 기준 9 files / 35 tests passed, npm run typecheck는 exit 0이었다.

Task R10.5 — 완료된 후속 계약의 현재 문서·dead scaffold 정리

Goal 실행 P10-R5: README와 미사용 admin page에서 완료된 Phase 3·10 기능을 아직 대기·구현 대상으로 설명하는 현재형 문구를 제거한다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-006

  • 시작 조건: OpenAPI 2.3.0의 lookup/FanTalk/Comments/Community operation implemented, P10-T1~T7 완료, AiCharactersPage production 사용처 0건.

  • 완료 증거: stale 문구와 dead export 사용처 검색, 현재 backend constraint 갱신, dead component 제거, 문서·typecheck·lint·build 검증.

  • Files: Modify: README.md, src/app/admin-pages.tsx; 필요 시 docs/20260725_AI캐릭터관리자웹/plan-task.md의 현재 상태 설명만 보완.

  • Interfaces: 실제 개발 API 수동 QA 대기와 제품 범위 밖 FanTalk 상세/filter/sort는 유지하고, 이미 구현된 v2 lookup·Phase 10 기능을 대기로 표시하지 않는다.

  • 범위 밖: 과거 날짜별 Decision/Progress 삭제, OpenAPI JSON 변경, 새 status page·문서 생성.

  • TDD 예외: rg로 README의 legacy lookup/Phase 10 구현 대상 문구와 미사용 AiCharactersPage export를 RED 대체 증거로 기록한다.

  • README Known Backend Constraints를 현재 계약과 수동 QA 대기 기준으로 갱신하고 미사용 Phase 3 placeholder component를 삭제한다.

  • 과거 구현 이력은 보존한 채 stale 현재형 문구 0건과 문서 링크·정적 Gate를 검증한다.

  • 실행 명령: rg -n 'legacy 후보|Phase 10 구현 대상|Phase 3에서|AiCharactersPage' README.md src/app docs/20260725_AI캐릭터관리자웹/plan-task.md; npm run typecheck; npm run lint; npm run build; git diff --check -- README.md src/app/admin-pages.tsx docs/20260725_AI캐릭터관리자웹/plan-task.md.

  • 기대 결과: 과거 기록을 제외한 stale 현재형 문구와 미사용 export 0건, 모든 명령 exit 0.

  • 수동 확인: README만 읽어 implemented 범위, 제품 제외 범위, 실제 개발 API 수동 QA 대기를 구분할 수 있는지 확인한다.

P10-R5 수정 검증 기록 (2026-07-30):

  • RED 대체: rg -n 'legacy 후보|Phase 10 구현 대상|Phase 3에서|AiCharactersPage' README.md src/app docs/20260725_AI캐릭터관리자웹/plan-task.md가 README 현재형 문구와 미사용 AiCharactersPage placeholder를 검출했다.
  • GREEN: README Known Backend Constraints를 OpenAPI 2.3.0 implemented 범위와 실제 개발 API 수동 QA 대기 기준으로 정리했고, src/app/admin-pages.tsx의 미사용 AiCharactersPage export를 삭제했다.
  • REFACTOR: 과거 날짜별 legacy 후보 이력과 review 관찰 내용은 보존하고, 현재 README에는 이전 후보 endpoint 표현만 남겼다.
  • 검증: rg -n 'legacy 후보|Phase 10 구현 대상|Phase 3에서|AiCharactersPage' README.md src/app는 no matches, npm run test:run -- src/app/App.test.tsx src/app/App.protected-errors.test.tsx는 2 files / 18 tests passed, npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. src/app/admin-pages.tsx LSP diagnostics는 오류 0건, 대상 문서·파일 git diff --check는 no output이었다.

Task R10.6 — 구현 완료 범위의 현재 문서 상태 동기화

Goal 실행 P10-R6: PRD·plan·현재 Phase 리뷰의 현재형 상태를 완료된 Phase 10 구현 및 후속 회귀 현황과 맞추고 과거 실행 기록은 보존한다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-007

  • 시작 조건: P10-T1~T7, P10-R3~R5 완료 기록과 2026-07-31 Phase별 재점검 결과.

  • 완료 증거: stale 현재형 문구 검색, PRD Overview·EXT 영향·plan 수용 기준·현재 review metadata/summary 최소 수정, Markdown link와 targeted diff 검증 통과.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/prd.md, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase1-platform-auth-shared-ui.md, phase5-series-management.md, phase8-comments.md, 필요 시 다른 현재 Phase 리뷰의 상태 표기.

  • Interfaces: 완료된 v2 lookup·Series CRUD·Community pagination·FanTalk PUT/DELETE·Comments를 미래 구현으로 쓰지 않는다. 실제 개발 API 수동 QA와 2026-07-31 신규 미완료 Task는 계속 구분한다. 과거 Decision/Progress/실패 기록은 삭제하지 않는다.

  • 범위 밖: OpenAPI JSON 변경, 애플리케이션 코드 수정, 과거 기록 재작성, 수동 QA를 완료로 표시.

  • TDD 예외: rg로 PRD의 “코드를 수정하지 않는다” 및 EXT의 “Phase 10에서 구현”, plan 현재 수용 기준의 미래형, 현재 리뷰 metadata/summary 불일치를 RED 대체 증거로 기록한다.

  • PRD·plan의 현재 상태와 현재 Phase 리뷰의 요약/종료 판정만 최신화하고 날짜별 이력은 보존한다.

  • Markdown link, stale 현재형 문구, git diff --check를 검증하고 수정 범위를 문서에 기록한다.

  • 실행 명령: rg -n '애플리케이션 코드는 이번 단계에서 수정하지 않는다|Phase 10에서 .*구현|신규 회귀 수정 대기' docs/20260725_AI캐릭터관리자웹; Markdown link 검사; git diff --check -- docs/20260725_AI캐릭터관리자웹.

  • 기대 결과: 과거 기록을 제외한 stale 현재형 문구와 상태 불일치 0건, 링크 오류 0건, diff 오류 0건.

  • 수동 확인: PRD Overview·EXT 표, plan §5.1·완료 정의, 각 현재 Phase 리뷰만 읽어 완료 기능·신규 회귀 Task·실제 server QA 대기를 구분할 수 있는지 확인한다.

P10-R6 수정 검증 기록 (2026-07-31):

  • RED 대체: rg -n "애플리케이션 코드는 이번 단계에서 수정하지 않는다|Phase 10에서 .*구현|신규 회귀 수정 대기|수정 대기|P10-GATE.*완료 아님" docs/20260725_AI캐릭터관리자웹가 PRD Overview, EXT 영향 문구, plan §3.1, Phase 1·8 리뷰 metadata, Phase 8 발견 요약, Phase 10 현재 리뷰 결론의 불일치를 검출했다.
  • GREEN: PRD Overview와 EXT 영향은 완료된 Phase 10 mock/client 구현과 실제 개발 API 수동 QA를 구분하도록 정정했다. plan §3.1은 Character/FanTalk/Comments의 현재 완료 범위로 갱신했고, Phase 1·8 리뷰 metadata와 Phase 8 요약 상태를 수정 완료로 맞췄다. 과거 Decision/Progress/실패 기록은 보존했다.
  • 검증: 현재 상태 대상 검색은 stale 현재형 문구 0건이었다. npm run typecheck, git diff --check -- docs/20260725_AI캐릭터관리자웹는 모두 exit 0이었다.

Task R10.7 — Phase별 현재 리뷰 상태·완료 이력 재동기화

Goal 실행 P10-R7: P9-R8 완료 뒤 Phase 0~10 현재 리뷰 metadata·요약·plan 상단 상태를 실제 수정 완료 이력과 외부 수동 QA 대기 상태에 다시 맞춘다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-008

  • 시작 조건: P9-R8 완료, P0-R3, P1-R10~R11, P4-R7, P8-R4, P9-R7의 기존 수정 완료 기록과 이번 Phase별 재리뷰 기록 보존.

  • 완료 증거: 현재 review metadata/최종 결론 불일치 검색, Phase 0~10 current status 최소 정정, 상대 링크와 plan 상태·검증 기록 점검.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase0-project-foundation.md, phase1-platform-auth-shared-ui.md, phase4-audio-content.md, phase8-comments.md, phase9-cross-cutting-quality.md, 필요 시 같은 reviews/phase*.md의 current metadata/summary.

  • Interfaces: 각 review의 과거 판정·실패·수정 기록은 삭제하지 않는다. 상단 리뷰 상태, 최신 차수 결론과 plan 상단 상태만 P9-R8 완료·실제 개발 API 수동 QA 대기를 구분하도록 정렬한다.

  • 범위 밖: OpenAPI·제품 코드 변경, 과거 기록 덮어쓰기, 실제 server QA를 완료로 표시, 기존 리뷰 파일 이름 변경.

  • TDD 예외: 문서 metadata 정합성 Task이므로 Phase별 상단 상태와 최신 Phase 결론·수정 후 검증을 대조한 실패 목록을 RED 대체 증거로 기록했다.

  • P9-R8 완료 상태와 실제 개발 API 수동 QA 대기를 반영해 current metadata·plan 상단 상태만 최소 정정했다.

  • Phase별 상대 링크, 현재 상태 검색, Markdown 구조와 git diff --check를 검증하고 결과를 누적했다.

  • 실행 명령: for f in docs/20260725_AI캐릭터관리자웹/reviews/phase*.md; do sed -n '1,16p' "$f"; tail -n 40 "$f"; done; rg -n '신규 회귀 수정 필요|교차 회귀 대기|P9-R8|P10-R7' docs/20260725_AI캐릭터관리자웹; Markdown 상대 링크 검사; git diff --check -- docs/20260725_AI캐릭터관리자웹.

  • 기대 결과: 과거 이력을 제외한 current status와 최신 결론 불일치 0건, P9-R8 완료와 실제 server QA 대기 구분, 링크·diff 오류 0건.

  • 수동 확인: 각 Phase 리뷰의 첫 화면과 최신 차수만 읽어 확정 finding, 수정 완료 여부, 남은 수동 QA를 오해 없이 구분할 수 있는지 확인한다.

P10-R7 수정 검증 기록 (2026-07-31):

  • RED 대체: for f in docs/20260725_AI캐릭터관리자웹/reviews/phase*.md; do sed -n '1,16p' "$f"; tail -n 25 "$f"; done로 Phase 0·1·4·8·9의 상단 리뷰 상태가 최신 수정 완료 결론과 어긋나거나 P9-R8 완료 전 상태를 유지하는 것을 확인했다.
  • GREEN: plan 상단 상태를 P9-R8·P10-R7 보완 완료와 실제 개발 API 수동 QA 대기로 정리했다. Phase 0·1·4·8·9 review metadata는 최신 수정 완료 상태로 맞췄고, Phase 9·10 현재 리뷰에는 P9-R8·P10-R7 수정 완료 판정과 검증 기록을 누적했다. 과거 판정·실패·수정 기록은 삭제하지 않았다.
  • 검증: rg -n '^\| 리뷰 상태 \|.*(수정 필요|교차 회귀 대기)|^- \*\*최종 결론:\*\*.*수정 필요|^- \*\*남은 항목:\*\*.*P[0-9]+-R' docs/20260725_AI캐릭터관리자웹/reviews/phase*.mdrg -n '^\| 상태 \|.*P[0-9]+-R' docs/20260725_AI캐릭터관리자웹/plan-task.md는 current metadata/latest conclusion 불일치 0건이었다. rg -n '\./reviews/[^)]+' docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/prd.md로 상대 링크 형식을 확인했고, git diff --check -- docs/20260725_AI캐릭터관리자웹는 exit 0이었다.

Task R10.8 — 완료된 Phase 10 후속 범위의 현재형 설명 정렬

Goal 실행 P10-R8: plan의 Phase 지도·현재 상태·수용 증거·Tech Stack과 Phase 10 리뷰 마지막 결론을 완료된 P10-T1~T7, P1-R12, P4-R8과 현재 설치 구성에 맞게 바로잡는다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-009

  • 시작 조건: P1-R13~R15, P6-R4, P9-R9~R10 완료 및 리뷰 상태 갱신, P10-T1~T7, P1-R12, P4-R8 완료 기록과 2026-07-31 문서 기준 재리뷰의 stale 현재형 검색 결과.

  • 완료 증거: FanTalk 수정·Audio UTC·Series CRUD·Community pagination·file policy를 미래형으로 쓰는 현재 설명 0건, 미설치 dependency를 Tech Stack으로 표시한 항목 0건, Phase 10 리뷰 최신 남은 Task 불일치 0건, link·diff 검증 통과.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md; Read: package.json; Test: 없음.

  • Interfaces: 과거 Phase 실행 당시의 계약 대기·실패·Decision/Progress는 삭제하지 않고, 현재 상태를 설명하는 상단 표·계획·최신 결론만 정정한다.

  • 범위 밖: 제품 코드·OpenAPI 변경, 과거 이력 재작성, 미사용 dependency 설치, 실제 개발 API 수동 QA를 완료로 표시.

  • TDD 예외 사유: 문서 현행화 Task이며 제품 동작 test로 의미 있는 RED를 만들 수 없다. stale 현재형·dependency 검색을 RED 대체 증거로 남긴다.

  • Phase 지도 행 7, §3.1 Phase 4~6, §5.1 Audio·Community·FanTalk·File 행, Tech Stack을 현재 완료 범위와 package.json에 맞게 최소 정정한다.

  • Phase 10 현재 리뷰 최신 남은 항목에서 이미 완료된 P1-R12, P4-R8을 제거하고 신규 회귀 Task·실제 서버 QA를 구분한다.

  • stale 검색·dependency 대조·link·diff 대체 검증 결과를 Progress와 Phase 10 리뷰에 누적한다.

  • 대체 검증 방법: current-state 특정 문구 검색, package.json dependency 대조, Markdown 상대 link 검사, targeted git diff --check.

  • 실행 명령: rg -n 'detail/edit/filter는 계약 대기|migration 필요|후속 구현|parser/UI 갱신 필요|P10-T[2457]에서' docs/20260725_AI캐릭터관리자웹/plan-task.md; node -e "const p=require('./package.json'); console.log(Object.keys({...p.dependencies,...p.devDependencies}).sort().join('\\n'))"; Markdown link 검사; git diff --check -- docs/20260725_AI캐릭터관리자웹.

  • 기대 결과: 과거 이력을 제외한 stale 현재형 0건, 현재 Tech Stack·dependency 불일치 0건, 최신 남은 Task 불일치 0건, link·diff 오류 0건.

  • 수동 확인: plan 상단·§3.1·§5.1과 Phase 10 리뷰 최신 차수만 읽어 완료 기능, 신규 회귀 Task, 실제 server QA 대기를 구분할 수 있는지 확인한다.

P10-R8 수정 검증 기록 (2026-07-31):

  • RED 대체: rg -n 'detail/edit/filter는 계약 대기|migration 필요|후속 구현|parser/UI 갱신 필요|P10-T[2457]에서|date-fns|dnd-kit|Lucide|Axios|React Router|React Hook Form|shadcn' docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md가 현재형 stale 문구와 미설치 dependency 표시를 검출했다.
  • GREEN: plan 상단 Tech Stack을 package.json 설치 구성과 현재 구현 방식(native history, fetch/XHR)에 맞췄고, Phase 지도 FanTalk 행과 §3.1 Phase 4~6을 완료된 Phase 10 범위와 실제 개발 API 수동 QA 대기로 정정했다. 과거 Progress와 review 근거 문장은 이력으로 보존했다.
  • 검증: node -e "const p=require('./package.json'); console.log(Object.keys({...p.dependencies,...p.devDependencies}).sort().join('\\n'))"로 설치 dependency를 대조했다. current-state stale 검색, targeted git diff --check, Phase 10 리뷰 상태 검색을 통과했다.

Task R10.9 — Goal 실행형 계획 필수 section 복구

Goal 실행 P10-R9: 기존 5,000여 행의 실행 이력을 보존하면서 goal-plan.md가 요구하는 목표, 현재 상태, 포함·제외, 기술 제약, Phase, 실행 순서·의존성, 변경 금지, 의사결정·중단, Progress, Decision Log, 발견된 문제, 최종 보고 형식을 명시적 section으로 찾을 수 있게 한다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-010

  • 시작 조건: P10-R8 완료, docs/agent-guide/goal-plan.md §3의 필수 12 section과 현재 plan의 최상위 heading 7개 대조.

  • 완료 증거: 필수 section 12개의 명시적 heading/navigation 존재, 기존 Task·Decision·Progress 이력 보존, 현재 신규 Task·중단 규칙·최종 보고 형식 연결, Markdown 구조·link·diff 검증 통과.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md; Read: docs/agent-guide/goal-plan.md, docs/sample/sample-plan-task.md; Test: 없음.

  • Interfaces: 과거 실행 기록을 삭제·압축·재번호화하지 않고, 필수 section에서 기존 신뢰할 수 있는 본문 위치와 현재 상태를 연결한다. 추가 보완은 새 이력으로만 남긴다.

  • 범위 밖: 제품 코드·OpenAPI·PRD 변경, 과거 Task 체크박스 재판정, 실제 서버 QA 완료 표시, 문서 전체 재작성.

  • TDD 예외 사유: 문서 구조 보완이며 제품 test는 확인 대상이 아니다. 필수 heading inventory 부족을 RED 대체 증거로 남긴다.

  • 가이드의 필수 12 section을 현재 본문과 mapping하고 없는 범위만 새 section으로 추가하며, 중복 본문은 링크/참조로 연결한다.

  • Progress, Decision Log, 발견된 문제, 최종 보고 형식에 신규 P1-R13~R15, P6-R4, P9-R9~R10, P10-R8~R9와 실제 API QA 대기를 현재 항목으로 연결한다.

  • heading·Task ID·link·diff 대체 검증 결과와 과거 이력 보존 확인을 Progress와 Phase 10 리뷰에 누적한다.

  • 대체 검증 방법: Node 또는 rg로 필수 heading 12개·중복 여부·Task ID 연결을 검사하고, 과거 체크박스·Progress 보존 diff와 Markdown link를 확인한다.

  • 실행 명령: rg -n '^## ' docs/20260725_AI캐릭터관리자웹/plan-task.md; 필수 section heading Node 검사; rg -n 'P1-R1[3-5]|P6-R4|P9-R(9|10)|P10-R[89]' docs/20260725_AI캐릭터관리자웹/plan-task.md; Markdown link 검사; git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md.

  • 기대 결과: 필수 section 12개 모두 탐색 가능, 중복 Task ID·broken link·whitespace 오류 0건, 과거 완료 체크박스·Progress 삭제 0건.

  • 수동 확인: 새 실행자가 상단 구조만 따라 읽어 현재 미완료 Task, 의존성, 금지 변경, 중단 조건, 보고 형식을 추측 없이 찾을 수 있는지 확인한다.

P10-R9 수정 검증 기록 (2026-07-31):

  • RED 대체: rg -n '^## ' docs/20260725_AI캐릭터관리자웹/plan-task.md 결과 필수 12 section 중 다수가 명시 heading으로 탐색되지 않았다.
  • GREEN: 기존 Task·Progress 이력을 삭제하지 않고 상단에 목표, 현재 상태, 범위의 포함·제외, 기술적 제약, 하나 이상의 Phase, 실행 순서와 의존성, 변경 금지 항목, 의사결정 및 중단 규칙, Progress, Decision Log, 발견된 문제, 최종 보고 형식 section을 추가해 기존 본문으로 연결했다.
  • 검증: 필수 section heading 검사, 신규 Task ID 검색, targeted git diff --check를 통과했다. 제품 코드와 OpenAPI는 변경하지 않았다.

Task R10.10 — Phase 10 현재형 설명·검증 명령 복구

Goal 실행 P10-R10: §5.1에 남은 완료 Task의 미래형 설명을 현재 상태로 고치고, P10-R8 stale 검사가 과거 이력과 명령 자체를 다시 매치하지 않게 한다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-011

  • 시작 조건: §5.1 Audio·Community·FanTalk·파일 정책 행이 완료된 P10-T2/T4/T5/T7을 미래 작업처럼 설명하고, P10-R8 exact rg가 과거 finding·Task 명령·RED 기록을 다시 찾아 exit 0과 여러 match를 반환하는 증거.

  • 완료 증거: §5.1을 구현 완료와 실제 server·수동 QA 대기로 구분한 현재형 설명, top 상태·Phase 지도·§5.1만 선택하는 실행 가능한 negative search, dependency 대조, 필수 section·Markdown link·diff 검증과 실제 exit/result 기록.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md; Test: 없음 — 검증 명령과 증거의 정합성 Task다.

  • Interfaces: 과거 REV-P10-009 근거와 P10-R8 RED 기록은 삭제하지 않는다. 현재 검증은 top current-state, Phase 지도와 §5.1처럼 명시적으로 한정된 section만 검사한다.

  • 범위 밖: 제품 코드·OpenAPI·dependency 변경, 과거 기록 삭제, 문서 전체 재작성.

  • TDD 예외 사유: 문서 검증 명령 자체가 산출물이므로 exact 기존 명령의 self/history match를 RED 대체 증거로 사용한다.

  • §5.1의 P10-T2/T4/T5/T7 미래형 행을 구현 완료와 실제 server·수동 QA 대기로 분리해 현재형으로 정정한다.

  • top current-state, Phase 지도와 §5.1만 선택하는 section-aware stale search로 현재 검증 기준을 정정한다.

  • dependency·필수 12 section·Markdown link·diff 검사를 재실행하고 실제 exit code와 결과를 기록한다.

  • Phase 10 리뷰에 기존 완료 판정을 바꾸지 않는 검증 증거 정정과 남은 항목을 누적한다.

  • 대체 검증 방법: section-aware negative search, 설치 dependency 목록 대조, heading·link·diff 검사.

  • 실행 명령: ! awk '(/^## 목표$/){current=1} (/^## 1\\. 전역 제약$/){current=0} (/^## 3\\. Phase 지도$/){current=1} (/^## 4\\. 파일 책임 지도$/){current=0} (/^### 5\\.1 P9 수용 기준 증거 요약$/){current=1} (/^## 6\\. 구현 완료 정의$/){current=0} current' docs/20260725_AI캐릭터관리자웹/plan-task.md | rg -n 'detail/edit/filter는 계약 대기|migration 필요|후속 구현|parser/UI 갱신 필요|UTC 전송·timezone 제거는 P10-T2|pagination object·timezone 제거는 P10-T4|답변 수정·팬 원글 삭제와 server integration은 P10-T5|P10-T7에서 종결|date-fns|dnd-kit|Lucide|Axios|React Router|React Hook Form'; dependency 대조 Node 명령; 필수 12 section 검사; Markdown link 검사; git diff --check -- docs/20260725_AI캐릭터관리자웹.

  • 기대 결과: section-aware stale match 0건, 현재 dependency·필수 section 불일치 0건, broken link·whitespace 오류 0건.

  • 수동 확인: P10-R8~R10과 Phase 10 리뷰 마지막 절에서 과거 stale 근거와 현재 통과 증거를 명확히 구분할 수 있는지 확인한다.

P10-R10 수정 검증 기록 (2026-07-31):

  • RED 대체: P10-R8의 문서 전체 stale 검색은 과거 finding·명령·RED 기록을 다시 매치하고, section-aware 검색도 §5.1의 UTC 전송·\timezone` 제거는 P10-T2, pagination object·`timezone` 제거는 P10-T4, 답변 수정·팬 원글 삭제와 server integration은 P10-T5, P10-T7에서 종결` 현재형 누락을 검출했다.
  • GREEN/REFACTOR: §5.1 Audio·Community·FanTalk·파일 정책 행을 구현 완료와 실제 개발 API 수동 QA 대기로 분리했다. 과거 P10-R8 근거와 Progress는 삭제하지 않고 현재 검증은 top current-state·Phase 지도·§5.1만 추출하는 section-aware negative search로 한정했다.
  • 검증: section-aware stale search — 0 matches. dependency 대조 Node 명령은 현재 설치 dependency 목록을 출력했고 미설치 dependency stale 표기는 없었다. 필수 section과 Markdown link는 기존 P10-R9 구조를 유지했다.

Task R10.11 — 현재형 검증 명령·완료 상태 재현성 복구

Goal 실행 P10-R11: P10-R10의 section-aware 검사를 문서에서 그대로 복사해 실행할 수 있게 만들고, 신규 회귀 Task 완료 뒤 plan·리뷰의 현재 상태를 실제 남은 Gate와 수동 QA에 맞춘다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-012

  • 시작 조건: P10-R10 원문 AWK heading 정규식의 이중 escape가 section 종료를 찾지 못해 과거 이력 10건을 다시 매치하고, dependency·heading·link 검사가 placeholder이며 plan 상단과 Phase 10 리뷰 metadata는 완료된 P1-R16/P9-R11/P10-R10을 수정 필요로 표시하는 근거.

  • 완료 증거: 복사 가능한 exact section-aware negative search exit 0·0 matches, 완전한 dependency·필수 section·link·diff 명령 결과, P1-R17/P9-R12 완료 뒤 plan top·Phase 10 리뷰 metadata·최신 Progress가 실제 자동 Gate/수동 QA 상태와 일치하는 기록.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md; Test: 없음 — 검증 명령과 current-state 문서의 재현성 Task다.

  • Interfaces: AWK heading은 shell copy에 안전한 [.] 표현을 사용한다. 과거 P10-R8~R10 finding·RED·Progress는 보존하고 top current-state와 최신 review metadata만 갱신한다.

  • 범위 밖: 제품 코드·OpenAPI·dependency 변경, 과거 검증 이력 삭제, WebKit/mobile Safari 실행.

  • Phase 10 Gate 연결: P1-R17, P9-R12 완료 뒤 exact 문서 명령, 필수 section·link·diff와 전체 unit·정적/build Gate가 통과하기 전에는 완료하지 않는다.

  • TDD 예외 사유: 문서 명령과 current-state metadata가 산출물이므로 exact negative search와 stale top 상태 검색을 RED 대체 증거로 사용했다.

  • AWK 정규식을 [.] 기반으로 유지하고 dependency·필수 section·Markdown link 검사를 placeholder 없는 완전한 명령으로 기록했다.

  • 원문 명령을 그대로 복사해 exit 0·0 matches와 dependency/section/link/diff 오류 0건을 확인했다.

  • P1-R17, P9-R12 완료 뒤 plan top·실행 순서·발견된 문제와 Phase 10 리뷰 metadata를 자동 Gate 완료 또는 실제 개발 API 수동 QA 대기로 전환했다.

  • 전체 unit·정적/build와 Chromium/mobile Chrome list 결과를 최신 Progress와 Phase 10 리뷰에 누적했다.

  • 대체 검증 방법: exact copy/paste command, dependency/heading/link Node 검사, full unit·정적/build와 Playwright list.

  • 실행 명령: ! awk '(/^## 목표$/){current=1} (/^## 1[.] 전역 제약$/){current=0} (/^## 3[.] Phase 지도$/){current=1} (/^## 4[.] 파일 책임 지도$/){current=0} (/^### 5[.]1 P9 수용 기준 증거 요약$/){current=1} (/^## 6[.] 구현 완료 정의$/){current=0} current' docs/20260725_AI캐릭터관리자웹/plan-task.md | rg -n 'detail/edit/filter는 계약 대기|migration 필요|후속 구현|parser/UI 갱신 필요|UTC 전송·timezone 제거는 P10-T2|pagination object·timezone 제거는 P10-T4|답변 수정·팬 원글 삭제와 server integration은 P10-T5|P10-T7에서 종결|date-fns|dnd-kit|Lucide|Axios|React Router|React Hook Form'; node -e 'const p=require("./package.json");const d={...p.dependencies,...p.devDependencies};const stale=["axios","date-fns","@dnd-kit/core","@dnd-kit/sortable","lucide-react","react-router-dom","react-hook-form"].filter(x=>x in d);if(stale.length){console.error(stale.join("\n"));process.exit(1)}'; node -e 'const fs=require("fs");const s=fs.readFileSync("docs/20260725_AI캐릭터관리자웹/plan-task.md","utf8");const required=["목표","현재 상태","범위의 포함·제외","기술적 제약","하나 이상의 Phase","실행 순서와 의존성","변경 금지 항목","의사결정 및 중단 규칙","Progress","Decision Log","발견된 문제","최종 보고 형식"];const missing=required.filter(h=>!s.includes("## "+h+"\n"));if(missing.length){console.error(missing.join("\n"));process.exit(1)}'; node -e 'const fs=require("fs"),path=require("path");const root="docs/20260725_AI캐릭터관리자웹";const files=[];const walk=d=>{for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):e.name.endsWith(".md")&&files.push(p)}};walk(root);const broken=[];for(const f of files){const s=fs.readFileSync(f,"utf8");for(const m of s.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)){const t=m[1].trim().replace(/^<|>$/g,"").split("#")[0];if(t&&!/^(https?:|mailto:)/.test(t)&&!fs.existsSync(path.resolve(path.dirname(f),t)))broken.push(f+": "+t)}}if(broken.length){console.error(broken.join("\n"));process.exit(1)}'; npm run test:run; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; npm run e2e:mock -- --list; npm run e2e -- --list; git diff --check.

  • 기대 결과: 모든 명령 exit 0, stale match·dependency/section/link/diff 오류 0건, full unit 0 failure, Playwright project는 chromium·mobile-chrome만 수집, top current-state와 최신 review 종료 판정 불일치 0건.

  • 수동 확인: 문서의 명령을 새 shell에 그대로 붙여 실행할 수 있고 상단 현재 상태만 읽어 다음 작업이 자동 Gate인지 실제 개발 API 수동 QA인지 판단 가능한지 확인한다.

P10-R11 수정 검증 기록 (2026-07-31):

  • RED 대체: [.] 기반 section-aware negative search는 0 matches였지만 plan 상단 상태·실행 순서·발견된 문제와 Phase 10 리뷰 metadata가 P10-R11 수정 필요를 유지하는 것을 확인했다.
  • GREEN/REFACTOR: plan top current-state를 P1-R17, P9-R12, P10-R11 수정 완료와 실제 개발 API 수동 QA 대기로 전환하고, Phase 10 리뷰 metadata와 종료 판정을 P10-R11 수정 완료로 정리했다. 과거 P10-R8~R10 finding·RED·Progress는 보존했다.
  • 문서 명령: section-aware negative search, dependency stale 검사, 필수 12 section 검사, Markdown local link 검사, git diff --check — 모두 exit 0 / no output.
  • 자동 Gate: npm run test:run — 81 files / 417 tests passed. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. build는 기존 500kB 초과 chunk warning만 표시했다. npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 8 tests passed. npm run e2e:mock -- --list — Chromium/mobile Chrome 104 tests 수집. npm run e2e -- --list — Chromium/mobile Chrome 18 tests 수집. 실제 E2E 실행은 사용자 지시에 따라 생략했다.

Task R10.12 — 최신 Progress·수동 QA current-state 종결

Goal 실행 P10-R12: 과거 재리뷰 기록은 보존하면서 plan 최하단에 최신 완료·후속 상태를 누적하고 상단 current-state가 모든 남은 문서 Task와 수동 QA를 빠짐없이 안내하게 한다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-013

  • 시작 조건: plan 상단은 P1-R17/P9-R12/P10-R11 완료를 선언했지만 최하단 최신 Progress는 세 Task를 남은 항목으로 유지하고, 상단 수동 QA에는 실제 crop pixel·stale ADMIN 확인이 빠진 재리뷰 근거.

  • 완료 증거: P9-R13 완료 뒤 plan top·실행 순서·발견된 문제와 최하단 최신 Progress가 자동 보완 완료 및 실제 crop pixel·stale ADMIN·개발 API 수동 QA 대기로 일치하고 Phase 10 리뷰 metadata·종료 판정도 같은 상태를 표시하는 기록.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md.

  • Interfaces: 과거 Progress·Decision·finding은 삭제하거나 재작성하지 않고 더 최신 기록만 append한다. 지원 browser는 Chromium/mobile Chrome만 유지한다.

  • 범위 밖: 제품·test·OpenAPI 변경, 수동 QA 완료 주장, 과거 browser 실행 이력 삭제.

  • Phase 10 Gate 연결: P9-R13 완료 뒤 top/tail current-state 검사, 필수 section·link·diff와 전체 unit·정적/build Gate가 통과하기 전에는 완료하지 않는다.

  • TDD 예외 사유: 문서의 시간순 current-state가 산출물이므로 top/tail 상태 검색과 상호 대조를 RED 대체 증거로 사용했다.

  • plan 최하단에 P1-R17/P9-R12/P10-R11/P9-R13 완료와 P10-R12 결과를 담은 최신 Progress를 append했다.

  • 상단 상태·현재 상태·실행 순서·발견된 문제에 실제 crop pixel·stale ADMIN·개발 API 수동 QA를 빠짐없이 반영했다.

  • Phase 10 리뷰 metadata·최신 종료 판정과 plan top/tail을 같은 상태로 맞췄다.

  • 문서 검사와 전체 unit·정적/build 결과를 최신 Progress와 Phase 10 리뷰에 누적했다.

  • 대체 검증 방법: top/tail current-state 대조, 필수 heading·Markdown link·diff 검사, full unit·정적/build Gate.

  • 실행 명령: tail -n 20 docs/20260725_AI캐릭터관리자웹/plan-task.md | rg -n 'P1-R17.*P9-R12.*P10-R11.*P9-R13|crop pixel.*stale ADMIN.*Series/FanTalk/Comments/file policy'; node -e 'const fs=require("fs");const s=fs.readFileSync("docs/20260725_AI캐릭터관리자웹/plan-task.md","utf8");const required=["목표","현재 상태","범위의 포함·제외","기술적 제약","하나 이상의 Phase","실행 순서와 의존성","변경 금지 항목","의사결정 및 중단 규칙","Progress","Decision Log","발견된 문제","최종 보고 형식"];const missing=required.filter(h=>!s.includes("## "+h+"\n"));if(missing.length){console.error(missing.join("\n"));process.exit(1)}'; node -e 'const fs=require("fs"),path=require("path");const root="docs/20260725_AI캐릭터관리자웹";const files=[];const walk=d=>{for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):e.name.endsWith(".md")&&files.push(p)}};walk(root);const broken=[];for(const f of files){const s=fs.readFileSync(f,"utf8");for(const m of s.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)){const t=m[1].trim().replace(/^<|>$/g,"").split("#")[0];if(t&&!/^(https?:|mailto:)/.test(t)&&!fs.existsSync(path.resolve(path.dirname(f),t)))broken.push(f+": "+t)}}if(broken.length){console.error(broken.join("\n"));process.exit(1)}'; npm run test:run; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; git diff --check.

  • 기대 결과: 모든 명령 exit 0, top/tail·review current-state 불일치와 필수 section·link·diff 오류 0건, 81 files / 417 tests 및 정적/build Gate 통과.

  • 수동 확인: plan 상단과 최하단만 읽어 다음 작업·지원 browser·모든 수동 QA 대기를 같은 내용으로 판단할 수 있는지 확인한다.

Task R10.13 — current-state 완료 계약·검증 명령 재현성 복구

Goal 실행 P10-R13: P10-R12의 top/tail/review 완료 상태를 정확히 같은 값으로 고정하고 과거 tail 행이나 OR match로 stale 상태가 통과하지 않는 실행 가능한 문서 계약을 만든다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-014

  • 시작 조건: Phase 10 리뷰와 plan 최하단은 P10-R12 완료를 선언하지만 plan 상단 다섯 current-state 위치와 Task 체크박스는 미완료이며, tail -n 20 | rg '완료|수동 QA' 명령이 이 충돌 상태에서도 과거 행·OR match로 exit 0인 재리뷰 근거.

  • 완료 증거: P9-R14~R15 완료 뒤 plan top·Task checklist·최신 Progress·Phase 10 metadata/종료 판정을 독립 scope로 검사하는 docs contract RED/GREEN, 기존 P10-R12 실행 명령 정정과 전체 unit·정적/build 결과.

  • Files: Modify: docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md, src/shared/mocks/__tests__/mock-preview-docs.test.ts.

  • Interfaces: 과거 Progress·finding은 보존하고 현재 top, P10-R12 checklist와 최신 review/Progress만 갱신한다. 지원 browser는 Chromium/mobile Chrome만 유지한다.

  • 범위 밖: 제품·OpenAPI 변경, 수동 QA 완료 주장, 과거 이력 삭제, WebKit/mobile Safari 실행.

  • Phase 10 Gate 연결: docs contract와 필수 section·link·diff, full unit·정적/build가 모두 통과하기 전에는 완료하지 않는다.

  • RED: plan top, P10-R12 checklist, 마지막 Progress marker 이후, Phase 10 metadata와 최신 종료 판정을 각각 추출하는 docs contract가 현재 불일치와 false-positive 명령을 검출하는지 확인했다.

  • GREEN: 현재 상태를 자동 보완 완료·모든 수동 QA 대기로 맞추고 P10-R12의 충족된 checklist를 완료 표시하며 OR 기반 tail 명령을 scoped contract 명령으로 교체했다.

  • REFACTOR: 과거 tail 행에 의존하는 assertion을 제거하고 P9-R14~R15 완료 후 실제 남은 항목만 current section에 유지했다.

  • docs contract와 전체 unit·정적/build 결과를 최신 Progress와 Phase 10 리뷰에 누적했다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run test:run; npm run typecheck; npm run lint; npm run build:dev; npm run build:prod; node -e 'const fs=require("fs");const s=fs.readFileSync("docs/20260725_AI캐릭터관리자웹/plan-task.md","utf8");const required=["목표","현재 상태","범위의 포함·제외","기술적 제약","하나 이상의 Phase","실행 순서와 의존성","변경 금지 항목","의사결정 및 중단 규칙","Progress","Decision Log","발견된 문제","최종 보고 형식"];const missing=required.filter(h=>!s.includes("## "+h+"\n"));if(missing.length){console.error(missing.join("\n"));process.exit(1)}'; git diff --check.

  • 기대 결과: docs contract 2 files / 9 tests, full unit 81 files / 418 tests, top/checklist/tail/review 불일치와 필수 section·diff 오류 0건, 정적/build exit 0.

  • 수동 확인: plan 상단과 마지막 Progress, Phase 10 최신 section만 읽어 자동 Task 완료·지원 browser·남은 수동 QA를 동일하게 판단할 수 있는지 확인한다.

P10-R13 수정 검증 기록 (2026-07-31):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts — 1 file / 1 failed / 5 passed. plan 최하단 최신 Progress marker가 없어 top/checklist/latest review 독립 scope 검사가 실패했다.
  • GREEN/REFACTOR: plan 상단 상태, 현재 상태, 실행 순서, 발견된 문제를 자동 보완 완료와 실제 crop pixel·stale ADMIN·Series/FanTalk/Comments/file policy 수동 QA 대기로 맞췄다. P10-R12 checklist와 P10-R13 checklist를 완료 표시하고, docs contract가 top/checklist/latest Progress/Phase 10 review를 독립 scope로 검사하게 했다. 과거 tail OR match 방식은 현재 완료 증거에서 사용하지 않는다.
  • 검증: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 9 tests passed. npm run test:run — 81 files / 418 tests passed. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, 필수 12 section Node 검사와 git diff --check — 모두 exit 0. build는 기존 500kB chunk warning만 표시했다.
  • 판정: P9-R14, P9-R15, P10-R13 자동 보완은 완료됐다. 남은 범위는 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA다. WebKit·Mobile Safari는 지원 범위에서 제외하고 Chromium/mobile Chrome만 유지한다.

Task R10.14 — Progress·review current-state 독립 scope 복구

Goal 실행 P10-R14: P10-R13 docs contract가 plan 상단 Progress, 해당 최신 Progress record, Phase 10 metadata, 최신 H2와 그 내부 종료 판정을 각각 닫힌 범위로 검사해 후속 기록 문자열로 stale 상태를 통과시키지 못하게 한다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-015

  • 시작 조건: contract가 plan 상단 ## Progress를 추출하지 않고, plan.slice(latestProgressIndex)P10-R13 marker부터 EOF까지 읽으며 Phase 10 리뷰 metadata와 ### 종료 판정도 독립 추출하지 않아 각 stale 상태를 후속 문자열로 통과하는 negative-control 근거.

  • 선행조건: P9-R16의 exact heading helper 수정 완료.

  • 완료 증거: plan 상단 ## Progress, exact Progress marker부터 다음 독립 Progress marker 또는 EOF까지의 범위, Phase 10 ## 1. 리뷰 정보, 최신 H2와 그 내부 종료 판정 각각의 assertion, synthetic 후속 record/H3 false positive 차단.

  • Files: Modify: src/shared/mocks/__tests__/mock-preview-docs.test.ts, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md.

  • Interfaces: 문서 contract helper만 최소 수정하고 제품·OpenAPI·Playwright 설정과 과거 Progress 이력은 변경하지 않는다.

  • 범위 밖: 문서 parser dependency, 수동 QA 완료 주장, WebKit/mobile Safari 실행.

  • Phase 10 Gate 연결: synthetic negative-control, docs contract, typecheck·lint·필수 section·link·diff 검사가 통과하기 전에는 완료하지 않는다.

  • RED: plan 상단 Progress를 stale하게 두거나 stale P10-R13 record 뒤 독립 후속 record에 필수 문구를 둬도 현재 contract가 통과하고 metadata·종료 판정 불일치를 놓치는 synthetic 실패를 고정했다.

  • GREEN: plan 상단 Progress를 독립 추출하고 exact marker 기반 record helper로 P10-R13 Progress를 다음 독립 record에서 닫으며 리뷰 metadata·최신 H2·종료 판정을 각각 추출했다.

  • REFACTOR: OR/EOF 범위와 review 전체 문자열 의존을 제거하고 현재 상태 토큰을 각 소유 section에서만 검사한다.

  • focused·정적·필수 section·link·diff 결과를 Progress와 Phase 10 리뷰에 누적한다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run typecheck; npm run lint; node -e 'const fs=require("fs");const s=fs.readFileSync("docs/20260725_AI캐릭터관리자웹/plan-task.md","utf8");const required=["목표","현재 상태","범위의 포함·제외","기술적 제약","하나 이상의 Phase","실행 순서와 의존성","변경 금지 항목","의사결정 및 중단 규칙","Progress","Decision Log","발견된 문제","최종 보고 형식"];const missing=required.filter(h=>!s.includes("## "+h+"\n"));if(missing.length){console.error(missing.join("\n"));process.exit(1)}'; node -e 'const fs=require("fs"),path=require("path");const root="docs/20260725_AI캐릭터관리자웹";const files=[];const walk=d=>{for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):e.name.endsWith(".md")&&files.push(p)}};walk(root);const broken=[];for(const f of files){const s=fs.readFileSync(f,"utf8");for(const m of s.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)){const t=m[1].trim().replace(/^<|>$/g,"").split("#")[0];if(t&&!/^(https?:|mailto:)/.test(t)&&!fs.existsSync(path.resolve(path.dirname(f),t)))broken.push(f+": "+t)}}if(broken.length){console.error(broken.join("\n"));process.exit(1)}'; git diff --check.

  • 기대 결과: docs contract 2 files / 9 tests, 후속 Progress/H3·metadata false positive 0건, 필수 12 section·link·diff·정적 오류 0건.

  • 수동 확인: plan 최신 Progress와 Phase 10 metadata·최신 종료 판정 각각에서 같은 다음 작업·지원 browser·수동 QA 상태를 읽을 수 있는지 확인한다.

P10-R14 수정 검증 기록 (2026-08-01):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts -t "keeps Phase 10 current state" — 1 file / 1 failed / 5 skipped. 최신 ## 25. P10-R14 수정 후 검증 — 2026-07-31 section 부재로 실패했다.
  • GREEN/REFACTOR: docs contract가 plan 상단 ## Progress, exact P10-R13 Progress record, Phase 10 ## 1. 리뷰 정보, 최신 H2와 내부 ### 종료 판정을 각각 닫힌 범위로 검사하게 했다. P10-R14 checklist와 Phase 10 review metadata·종료 판정을 수정 완료 상태로 갱신했다.
  • focused: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts -t "keeps Phase 10 current state" — 1 file / 1 passed / 5 skipped.
  • 검증: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 9 tests passed. npm run typecheck, npm run lint, 필수 12 section 검사, Markdown link 검사, git diff --check — 모두 exit 0. npm run e2e:mock -- --list는 Chromium/mobile Chrome 104 tests, npm run e2e -- --list는 Chromium/mobile Chrome 18 tests를 수집했다. 개발 중 정책에 따라 E2E 실제 실행은 하지 않았다.

Task R10.15 — §7 최신 Progress와 실제 marker scope 복구

Goal 실행 P10-R15: docs contract가 Task 본문의 P10-R13 검증 기록이 아니라 §7의 실제 P10-R13 Progress와 새 current 완료 record를 각각 닫힌 범위로 검사하고 top/review 상태와 일치시킨다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-016

  • 시작 조건: contract가 §7의 **P10-R13 수정 검증 기록 — 2026-07-31:** 대신 Task 본문의 괄호형 marker를 선택해 §7 record를 stale로 바꿔도 통과하고, plan 최하단은 여전히 P9-R16P10-R14를 남은 항목으로 유지하는 negative-control 근거.

  • 선행조건: P9-R17 완료.

  • 완료 증거: P9-R16·P10-R14 checklist 완료, §7 실제 P10-R13 marker의 다음 record 경계, P9-R17·P10-R15 완료를 담은 append-only 최신 record, plan top과 Phase 10 metadata·실제 마지막 H2·종료 판정의 동일 current-state contract 및 날짜 정정 기록.

  • Files: Modify: src/shared/mocks/__tests__/mock-preview-docs.test.ts, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md.

  • Interfaces: 기존 progressRecordAtMarker()와 exact heading helper를 재사용한다. 과거 Progress와 잘못 기재된 review 날짜는 삭제하지 않고 정정 section을 append한다.

  • 범위 밖: 제품·OpenAPI·Playwright 설정 변경, 과거 기록 삭제, 수동 QA 완료 주장, WebKit/mobile Safari 실행.

  • Phase 10 Gate 연결: 실제 §7 marker negative-control, 최신 record·metadata·최신 결론 contract, docs test·정적·필수 section·link·diff가 통과하기 전에는 완료하지 않는다.

  • RED: P9-R16·P10-R14 checklist 또는 §7 P10-R13 record·plan 최하단을 stale하게 바꿔도 Task-local marker 기반 contract가 통과하고 trailing H2를 놓치는 실패를 고정했다.

  • GREEN: 두 current Task checklist를 검사하고 contract를 §7 em-dash marker로 옮겨 다음 record에서 닫으며, 이번 수정 완료·수동 QA만 담은 최신 Progress record와 Phase 10 정정 H2를 append해 실제 마지막 H2를 대상으로 삼았다.

  • REFACTOR: Task-local 검증 기록을 current Progress 대체물로 쓰는 assertion을 제거하고 top/tail/metadata/최신 결론을 각 소유 section에서만 검사한다.

  • focused·정적·필수 section·link·diff·Playwright list 결과를 Progress와 Phase 10 리뷰에 누적했다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run typecheck; npm run lint; npm run e2e:mock -- --list; npm run e2e -- --list; node -e 'const fs=require("fs");const s=fs.readFileSync("docs/20260725_AI캐릭터관리자웹/plan-task.md","utf8");const required=["목표","현재 상태","범위의 포함·제외","기술적 제약","하나 이상의 Phase","실행 순서와 의존성","변경 금지 항목","의사결정 및 중단 규칙","Progress","Decision Log","발견된 문제","최종 보고 형식"];const missing=required.filter(h=>!s.includes("## "+h+"\n"));if(missing.length){console.error(missing.join("\n"));process.exit(1)}'; git diff --check.

  • 기대 결과: docs contract 2 files / 9 tests, Task-local/§7 marker 혼동과 top/tail/review 불일치 0건, mock 104·server 18 tests가 chromium·mobile-chrome에서만 수집되고 정적·필수 section·link·diff 오류 0건.

  • 수동 확인: plan 상단·최하단 최신 record와 Phase 10 metadata·마지막 H2에서 같은 완료 Task·지원 browser·남은 수동 QA를 읽을 수 있는지 확인한다.

P10-R15 수정 검증 기록 (2026-08-01):

  • RED: npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts -t "keeps Phase 10 current state" — 1 file / 1 failed / 5 skipped. §7 최신 P9-R17·P10-R15 완료 record가 없어 실패했다.
  • GREEN/REFACTOR: docs contract가 P9-R16·P10-R14 checklist, §7 em-dash형 P10-R13 record, 최신 P9-R17·P10-R15 record, Phase 10 metadata와 실제 마지막 H2·종료 판정을 각각 닫힌 범위로 검사하게 했다. plan top current-state와 Phase 10 review metadata를 자동 보완 완료 및 수동 QA 대기로 맞췄다.
  • 검증: focused GREEN은 1 file / 1 passed / 5 skipped, docs contract는 2 files / 9 tests passed, 전체 unit은 81 files / 418 tests passed였다. typecheck, lint, 필수 12 section 검사, Markdown link 검사, git diff --check는 모두 exit 0이다. npm run e2e:mock -- --list는 Chromium/mobile Chrome 104 tests, npm run e2e -- --list는 Chromium/mobile Chrome 18 tests를 수집했다. 개발 중 정책에 따라 실제 E2E 실행은 하지 않았다.

Task R10.16 — Phase 10 finding·checklist·최신 Progress contract 종결

Goal 실행 P10-R16: P10-R15 완료를 소유 finding 상태와 Task checklist에서 확인하고, §7의 실제 마지막 독립 Progress record를 동적으로 검사한다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-017.

  • 시작 조건: REV-P10-016 표는 확정 상태이고 docs contract는 P10-R15 자체 checklist를 검사하지 않는다. 최신 Progress marker를 P9-R17·P10-R15로 고정해 뒤에 새 record가 추가돼도 실제 마지막 record를 보지 않으며 synthetic fixture도 과거 괄호형 Task-local marker를 사용한다.

  • 선행조건: P9-R18, P9-R19 완료.

  • 완료 증거: REV-P10-016수정 완료 상태·날짜별 근거, P10-R15 checklist assertion, §7의 실제 마지막 독립 Progress record를 선택하는 helper와 em-dash형 marker 기반 synthetic negative-control.

  • Files: Modify: src/shared/mocks/__tests__/mock-preview-docs.test.ts, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md.

  • Interfaces: 기존 line/record helper를 최소 확장한다. 제품·OpenAPI·Playwright 설정은 변경하지 않는다.

  • 범위 밖: Markdown 전체 parser, 과거 Progress 삭제, 수동 QA 완료 주장, WebKit/mobile Safari 실행.

  • RED: P10-R15 checklist를 미완료로 바꾸거나 고정 marker 뒤 stale 최신 record를 append해도 contract가 통과하는 실패를 고정한다.

  • GREEN: 소유 finding 상태와 P10-R15 checklist를 검사하고 §7의 실제 마지막 독립 record를 선택한다.

  • REFACTOR: synthetic marker를 실제 em-dash형 §7 형식으로 통일하고 과거 Task-local 괄호형 의존을 제거한다.

  • docs contract·typecheck·lint·필수 section·link·diff 결과를 Progress와 Phase 10 리뷰에 누적한다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run typecheck; npm run lint; 필수 12 section 검사; Markdown link 검사; git diff --check.

  • 기대 결과: finding/checklist/latest Progress 불일치와 marker 형식 false positive 0건, docs contract·정적·문서 검사 오류 0건.

P10-R16 수정 검증 기록 (2026-08-01):

  • RED: 고정 marker synthetic은 1 failed / 8 skipped, current-state focused는 plan 상단 보완 대기 상태로 1 failed / 8 skipped였다.
  • GREEN/REFACTOR: §7의 마지막 em-dash 날짜형 독립 Progress를 선택하고 실제 marker 형식, P10-R15 checklist와 REV-P10-016·REV-P10-017 상태를 검사했다. focused 2 passed / 7 skipped, docs contract 2 files / 12 tests passed였다.

Task R10.17 — 중복 Progress marker의 마지막 record 보장

Goal 실행 P10-R17: §7에 동일 제목·날짜 marker가 반복돼도 실제 마지막 occurrence의 Progress record를 검사한다.

  • 연결 리뷰: Phase 10 현재 리뷰REV-P10-018.

  • 시작 조건: latestProgressRecord()는 마지막 marker 문자열을 찾지만 progressRecordAtMarker()findIndex()로 첫 동일 문자열을 다시 선택해 오래된 완료 record를 반환하는 근거.

  • 선행조건: P10-R16 완료.

  • 완료 증거: 동일 marker의 첫 record가 완료, 마지막 record가 보완 필요인 synthetic negative-control과 마지막 occurrence index 기반 record 추출.

  • Files: Modify: src/shared/mocks/__tests__/mock-preview-docs.test.ts, docs/20260725_AI캐릭터관리자웹/plan-task.md, docs/20260725_AI캐릭터관리자웹/reviews/phase10-openapi-follow-up.md.

  • Interfaces: 새 parser나 helper 계층 없이 기존 progressRecordAtMarker()가 동일 marker의 마지막 occurrence를 선택하게 한다.

  • 범위 밖: 제품·OpenAPI·Playwright 설정 변경, 과거 Progress 삭제, WebKit/mobile Safari 실행.

  • RED: 동일 marker가 두 번 나오고 마지막 record만 보완 필요인 synthetic 문서가 과거 완료 record를 반환하는 실패를 고정한다.

  • GREEN: marker 검색을 뒤에서 수행해 마지막 occurrence부터 다음 독립 record 또는 EOF까지 반환한다.

  • REFACTOR: latestProgressRecord()의 marker 형식·record 경계 helper를 그대로 재사용하고 추가 추상화는 만들지 않는다.

  • docs contract·전체 unit·typecheck·lint·link·diff 결과를 Progress와 Phase 10 리뷰에 누적한다.

  • 실행 명령: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts; npm run test:run; npm run typecheck; npm run lint; Markdown link 검사; git diff --check.

  • 기대 결과: 중복 marker stale record 선택 0건, docs contract·전체 unit·정적·문서 검사 오류 0건.

P10-R17 수정 검증 기록 (2026-08-01):

  • RED: 동일 marker 두 개 중 첫 완료 record를 반환해 latest Progress synthetic이 1 failed / 8 skipped였다. current-state 전환은 plan 상단의 P10-R17 대기 상태로 1 failed / 8 skipped였다.
  • GREEN/REFACTOR: progressRecordAtMarker()가 marker를 뒤에서 찾아 마지막 동일 occurrence를 사용하게 했다. 별도 parser·dependency·추가 helper는 만들지 않았다.
  • 검증: Phase 10 focused 2 passed / 7 skipped, docs contract 2 files / 12 tests, 전체 unit 81 files / 421 tests가 통과했다. typecheck, lint, 필수 section·Markdown link·git diff --check도 exit 0이었고 독립 재리뷰에서 신규 Critical/Important/Minor 없음으로 승인됐다.

Phase 10 Gate

Goal 실행 P10-GATE: OpenAPI 2.3.0 후속 범위의 unit·mock·server allowlist·build와 문서 추적을 최종 판정한다.

  • 시작 조건: P10-T1~P10-T7 완료.
  • 완료 증거: 아래 자동 명령과 수동 QA 대기 항목을 §7 검증 기록에 누적하고 요구사항 추적표·현재 상태를 갱신한다.
  • 범위 밖: mock 성공이나 server spec skip을 실제 server 완료로 처리하는 것과 제품 범위에서 제외한 FanTalk 상세/filter/sort·유일성 오류 분기.
npm run test:run -- src/features/characters src/features/audio-contents src/features/series src/features/community-posts src/features/fan-talks src/features/comments src/shared/mocks
npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/series.spec.ts tests/e2e/community.spec.ts tests/e2e/fan-talk.spec.ts tests/e2e/comments.spec.ts tests/e2e/error-mapping.spec.ts
npm run e2e
npm run typecheck
npm run lint
npm run build
git diff --check

기대 결과: 모든 자동 Gate 명령 exit 0이고, mock/contract 범위에서 legacy lookup·timezone request 0건, 예약 releaseDate UTC Z, Community pagination metadata 일치, FanTalk 두 번째 POST·PUT isActive 0건, Comments 3단계·팬 댓글 PUT 0건, 0..99999 밖의 price request 0건, 미정의 오류 기본 문구 불일치 0건이다. FanTalk 답변 PUT을 포함한 제공 operation의 실제 server 결과는 E2E용 ADMIN 계정을 제공하지 않는 정책상 자동 Gate 완료 조건에서 제외하고 수동 QA 대기 항목으로 분리 기록한다.

수동 확인: 320/768/1280px, keyboard-only, 200% zoom, axe critical·serious 0건과 mock/server 상태 분리를 확인한다. E2E용 ADMIN 계정을 제공하지 않으므로 실제 개발 API 검증은 운영자가 수동 QA로 수행하고, mock 결과만으로 server 완료 표시를 하지 않는다. 수동 QA 목록은 다음과 같다.

  • Series 목록에는 비활성 series가 보이지 않는다.
  • Series 생성에서 장르 선택, 210:297 JPEG/PNG cover crop, 필수 입력 검증, 생성 후 목록 복귀와 새 항목 표시를 확인한다.
  • Series 수정에서 상세 원본값 초기화, 상태 변경 저장, optional image 교체 없이 저장, 비활성화 후 목록 제거를 확인한다.
  • Series 상세에서 오디오 후보 검색, 연결, body 없는 연결 해제, 전체 순서 변경 저장과 재진입 후 순서 유지가 동작한다.
  • FanTalk 목록에서 기존 답변 item은 수정 UI, 미답변 item은 등록 UI로 열린다.
  • FanTalk 답변 수정은 content만 저장하고 저장 후 목록 재조회 결과가 반영된다.
  • FanTalk 팬 원글 삭제는 확인 dialog 뒤 목록에서 제거되고 별도 상세 GET이 발생하지 않는다.
  • FanTalk 미답변 item에 동시 POST를 시도해도 활성 creator reply는 1개만 남는다.
  • 실제 개발 API에서 파일 용량 초과와 MIME/확장자 불일치가 서버에서도 거부되는지 확인한다.

Phase 10 Decision Log

날짜 결정
2026-07-29 완료된 Phase 3~9를 다시 열지 않고 OpenAPI 2.3.0 후속 구현을 Phase 10으로 분리한다.
2026-07-29 FanTalk reply PUT의 path replyIdcreatorReplies[].fanTalkId를 사용하고, 댓글 수정 가능 여부는 writerId === creatorId로 판정한다.
2026-07-29 팬 작성 FanTalk 원글 DELETE를 이번 후속 범위에 포함하고, reply PUT의 planned 상태는 mock/client와 server 완료를 분리한다.
2026-07-29 FanTalk reply PUT은 backend 구현이 완료됐고 OpenAPI status만 누락됐다는 사용자 확인에 따라 implemented로 정정한다. P10-T5는 mock/client/server integration 전체를 완료 조건으로 사용한다.
2026-07-29 현재 FanTalk UI에는 별도 상세 GET·답변 상태 filter·sort query가 필요하지 않고 중복 오류 전용 key도 client 분기에 사용하지 않으므로 EXT-004를 해결로 종결한다. 답변 1개 불변식은 정확한 오류 key가 아니라 동시 POST 후 재조회 결과로 server에서 검증한다.
2026-07-29 Phase 5의 미구현 Series CRUD를 P10-T3에서 실행할 때 JPEG/PNG·10MB·210:297·최대 1,000px·no-upscale·세로 1px 오차와 생성 필수/수정 optional image를 함께 구현한다.
2026-07-29 Phase 8의 미구현 댓글 UI·CRUD·권한·접근성 범위는 추가 계약 대기 없이 P10-T6에서 구현한다. 과거 P8-T2~T4 체크박스는 계약 부재 당시 이력으로 보존하고 다시 열지 않는다.
2026-07-29 Series·FanTalk·Comments server integration은 각 spec을 server testMatch에 포함하고 focused server 명령이 실제 spec을 실행해야 완료로 인정한다. No tests found, mock 성공, 전용 fixture 부재에 따른 skip은 server 완료 증거가 아니다.
2026-07-29 EXT-009~011의 가격 0..99999, 미정의 오류 공통 문구, 기존 파일 client 검증과 backend 확인 경계를 P10-T7로 추가한다.
2026-07-30 E2E용 ADMIN 계정은 제공하지 않는 정책이므로 Series/FanTalk 실제 server integration은 자동 Gate 완료 조건에서 제외하고 수동 QA 체크리스트로 추적한다. mock 성공이나 session 주입을 server 완료 증거로 대체하지 않는다.

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 MOCK-001~009, §11.111.2, §1213 src/shared/mocks, tests/e2e/mock-preview-shell.spec.ts
3 CHAR-001~018, Character 관련 FILE, MOCK, §7, §9 src/features/characters, tests/e2e/character-workspace.spec.ts
4 AUDIO-001~034, Audio 관련 FILE, MOCK, §9 src/features/audio-contents, tests/e2e/audio-content.spec.ts
5 SERIES-001~018, Series 관련 FILE, MOCK, §9 src/features/series, tests/e2e/series.spec.ts
6 COMMUNITY-001~015, Community 관련 FILE, MOCK, §9 src/features/community-posts, tests/e2e/community.spec.ts
7 FANTALK-001~011, MOCK, §9 src/features/fan-talks, tests/e2e/fan-talk.spec.ts
8 COMMENT-001~006, MOCK, §9 계약 부재 조사 이력
9 §910, §1214, 활성 범위 전체 전체 unit/integration/mock·server E2E/build
10 CHAR-012~014, Audio UTC·AUDIO-019, SERIES-001~018, COMMUNITY-007~008, COMMUNITY-010, COMMUNITY-012, FANTALK-003~012, COMMENT-001~008, FILE-001~015, EXT-001~005, EXT-007~011 도메인 focused test, 7개 mock E2E, server integration, P10 Gate

FILE-001~015의 domain-neutral component mechanics는 Phase 1에서 먼저 만들고 browser mock 기반은 Phase 2가 소유한다. Character·Audio·Series·Community Phase는 자신의 allowed type·crop profile·GIF 예외, multipart handler와 fixture 흐름을 소유하며 Phase 9에서 mock/server 전체 matrix를 회귀 검증한다.

5.1 P9 수용 기준 증거 요약

PRD 수용 기준 증거 상태
AUTH-001~013, PRD 14.1 로그인·세션·로그아웃 Phase 1 Gate, tests/e2e/auth.spec.ts, P9 server-boundary 재검증 활성 범위 검증 완료
CHAR-001~018와 Character image/file 기준 Phase 3 Gate, tests/e2e/character-workspace.spec.ts, P9 resource/error regression, P10-T1 기존 mock UI 완료. EXT-001, EXT-007 해결; v2 원작 선택기는 P10-T1에서 완료, active-only server 검증은 P10-GATE에서 분리
AUDIO-001~034와 Audio file/player 기준 Phase 4 focused/mock 기록, tests/e2e/audio-content.spec.ts, P9 resource/error regression, P10-T2, P4-R9 기존 mock UI와 UTC 전송·timezone 제거 client/mock 완료. compact audio-only 공용 player UI는 P4-R9에서 구현·V3 시각 QA 완료했고 실제 개발 API Audio 수동 QA는 별도 대기
SERIES-001~018 Phase 5 focused/mock 기록, tests/e2e/series.spec.ts, P9 resource/error regression, P10-T3 연결·순서와 v2 장르·CRUD mock/client 완료. server Series E2E는 로그인 fixture 차단으로 P10-GATE에서 분리
COMMUNITY-001~015 Phase 6 focused/mock 기록, tests/e2e/community.spec.ts, P9 resource/error regression, P10-T4 기존 mock UI와 pagination object·timezone 제거 client/mock 완료. 실제 개발 API Community 수동 QA는 별도 대기
FANTALK-001~012 Phase 7 focused/mock 기록, tests/e2e/fan-talk.spec.ts, P9 resource/error regression, P10-T5 목록·답변 생성·답변 수정·팬 원글 삭제 mock/client 완료. 실제 개발 API FanTalk 수동 QA는 별도 대기
COMMENT-001~008 P8 계약 부재 이력, OpenAPI 2.3.0 댓글 operation, P10-T6 EXT-005 해결; 댓글 UI·fixture와 focused server route 검증은 P10-T6에서 완료
AUDIO-019, COMMUNITY-007, FILE-001~015, EXT-009~011 기존 Phase 1·4·6·9 회귀와 P10-T7 가격 상한·미정의 오류·파일 client 경계 완료. backend 동일 검증과 실제 개발 API file policy 수동 QA는 별도 대기
PRD 9, 10.6~10.7, 14.2 반응형·접근성 P9-T2 responsive-capabilities.spec.ts, accessibility.spec.ts Playwright local matrix 완료, Edge/실기기 최신 2개 major는 릴리스 QA
PRD 12, MOCK-001~009, 14.1 mock/server 경계 Phase 2 Gate, P9-T1/T2, server-boundary E2E mock UI와 실제 server integration 상태 분리 유지

6. 구현 완료 정의

아래 체크리스트는 개별 Task가 아니라 최종 Gate 완료 정의다. Phase 10 P10-T1~P10-T7, 과거 Phase 후속 구현, 자동 Gate 정리는 완료됐지만, 실제 개발 API credential·fixture가 필요한 수동 QA는 별도 대기 상태다. 따라서 최종 릴리스 완료 정의는 아직 체크하지 않는다.

  • Phase 0~9의 보존된 활성 범위와 Phase 10 후속 Gate가 최신 실행에서 통과한다.
  • 모든 확정 요구사항이 구현, 명시적 non-goal, 또는 결정 기록이 있는 후속 범위 중 하나로 추적된다.
  • 외부 의존을 추정 endpoint·placeholder DTO·임시 production mock으로 우회하지 않았다.
  • 제공 계약 범위는 dev:mock에서 최종 UI를 확인할 수 있고, 계약 미제공 범위는 fixture로 추정하지 않았다.
  • 각 도메인의 UI 확인 완료(mock)실제 서버 연동 완료(server) 상태와 증거가 분리돼 있다.
  • server mode의 404·network error가 mock으로 자동 fallback되지 않고 production build에서 browser 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. 검증 기록

구현 단계마다 아래 형식으로 누적하고 기존 기록을 삭제하거나 덮어쓰지 않는다.

계약 이력 안내 (2026-07-28): 아래 2026-07-27 기록의 API Contract §... 표기는 당시 사용한 삭제 전 api-contract.md의 section을 가리킨다. 현재 Phase 3 이후 구현 기준은 api-contract.openapi.json이며 과거 section 표기는 실행 당시 근거를 보존하기 위해 수정하지 않는다.

2026-07-29 추가: OpenAPI 2.3.0 후속 계약은 Phase 10이 소유한다. 아래 Phase 3~9의 외부 의존·제외 기록은 실행 당시 이력이며 현재 상태는 §3.1, Phase 10과 PRD §11.5를 우선한다.

### 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.mdrg -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-onlydocs/20260725_AI캐릭터관리자웹/plan-task.md 1개만 확인, 애플리케이션 코드 변경 0건.
    • 애플리케이션 test/build — 이번 단계는 계획 전용이고 아직 package.json이 없어 실행하지 않음.
  • 남은 항목: 후속 구현 전체와 각 도메인에 기록한 backend 외부 의존 계약.

Task 1.1 구현 — 2026-07-26

  • 무엇을: PRD 10.1~10.9 기반의 밝은 관리자 디자인 시스템을 DESIGN.mdsrc/styles/globals.css에 문서화·구현하고, Tailwind CSS Vite 플러그인과 shadcn/ui CSS variable mode components.json을 최소 구성했다. StatusBadgeIconOnlyAction 원시 컴포넌트만 추가했다.
  • 왜: 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. Root lang="ko"mainAI 캐릭터 관리자를 실제 브라우저에서 확인했다.
    • 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.9 UX 검색 기록을 보강했으며, 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/--card alias 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. Root lang="ko"mainAI 캐릭터 관리자 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건.
  • 남은 항목: Task 1.1 없음. .gitignore는 작업 전 dirty 상태였고 이번 수정에서 건드리지 않았다.

Task 1.2 구현 — 2026-07-26

  • 무엇을: sessionStorage 기반 ADMIN session adapter, Zod envelope parser와 ApiError/AccessDeniedError API 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 zodnpm 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:devnpm 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/ 접근 — 성공. 페이지 title AI 캐릭터 관리자main > h1 shell 렌더를 확인했다. console은 기존 favicon 미구현에 따른 /favicon.ico 404만 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.json listFiles에는 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 — 성공.
  • 남은 항목: 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, /login route 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, LoginPage import 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 제거·/login navigation·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.ts 27, auth-session-context.ts 24, auth-session.tsx 53, login-schema.ts 6, LoginPage.tsx 118, auth-api.test.ts 89, auth-session.test.tsx 130, login-page.test.tsx 36으로 모두 200 LOC 이하.
  • 남은 항목: Task 1.3 없음. LoginPage browser visual QA는 아직 route/App surface에 연결하지 말라는 Task 1.3 범위 제한 때문에 RTL과 기존 root smoke로만 검증했고, 실제 /login route browser QA는 Task 1.4에서 수행한다.

Task 1.4 Wave 2 구현 — 2026-07-26

  • 무엇을: /ai-characters에 ADMIN session 전용 shell, /access-denied route, 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 내부 동기 setState lint를 비동기 성공 경로로 옮긴 뒤 재실행했다.
    • 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-characters redirect, 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.tsx 214, App.test.tsx 136, browser-location.ts 24, route-paths.ts 6. App.tsx는 200-250 경고 구간이므로 다음 shell 확장 전 분리 후보로 남긴다.
  • 남은 항목: 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.md Section 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. 주입 maxBytes 10MB 경계, audio 1,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/sharedsrc/shared/ui directory 진단 0건. 일부 개별 file fresh 진단은 timeout이었고 TypeScript LSP alive, npm run typecheck, npm run build 성공으로 보완 확인했다.
    • changed file pure LOC — 최대 admin-audio-player.tsx 122, image-crop-dialog.tsx 118로 모두 200 LOC 이하.

Goal 실행 계약 보완 — 2026-07-27

  • 무엇을: 완료된 Phase 01을 포함한 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 Goal P[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 제공 대기 계약.

Mock Preview Phase 추가 및 후속 Phase 재번호 — 2026-07-27

  • 무엇을: 제공된 API Contract 범위에서 backend 구현 전 최종 UI를 확인할 수 있도록 개발 전용 browser MSW 요구사항과 계약 경계를 추가하고, 완료된 Phase 01 다음에 신규 Phase 2를 배치했다. 기존 Character최종 인수인계 Phase는 Phase 3~9로 이동하고 모든 활성 Task·Goal·Gate·의존성·추적표를 함께 재번호했다.
  • 왜: 실제 API의 404를 mock으로 자동 대체하지 않으면서도 frontend가 최종 화면을 개발·검토할 수 있고, mock UI 확인과 실제 server integration 완료를 혼동하지 않기 위해서다.
  • 어떻게:
    • PRD MOCK-001~009, API Contract §1.5, Phase 2의 Task 2.1~2.3과 Gate 대조 — explicit server | mock mode, production 차단, no-auto-fallback, deterministic in-memory store와 mock banner 규칙 일치 확인.
    • 활성 계획 검사 — Phase·Phase Goal·Gate·Gate Goal 각각 10개, 일반 구현 Task·Task Goal 각각 37개, Goal 선언 중복 0건, 활성 영역의 이전 Phase 번호 참조 0건.
    • 문서 동기화 검사 — docs/agent-guide/{prd,goal-plan}.mddocs/sample/{sample-prd,sample-plan-task}.md 4개 모두 같은 mock 작성 규칙 포함, 미구현 docs/agent-guide/{environment,scripts}.mddev:mock·e2e:mock·VITE_API_MODE 선반영 0건.
    • Markdown fence 짝수 여부와 수정 문서의 내부 link 검사 — 이상 0건.
    • git diff --check -- <수정 문서 7개> — 성공.
    • 애플리케이션 test/build는 구현 코드와 설정을 변경하지 않았고 신규 명령도 아직 계획 상태이므로 실행하지 않는다.
  • 남은 항목: Phase 2 구현 시 실제 dev:mock·e2e:mock 명령과 환경 변수를 만든 후 README와 docs/agent-guide/{environment,scripts}.md를 실체에 맞게 갱신한다. 계약 미제공 도메인은 backend 계약 수신 전 fixture를 만들지 않는다.

Phase 2 코드 리뷰·QA — 2026-07-27

  • 무엇을: Phase 2 staged 구현 28개 경로를 PRD MOCK-001~009, API Contract §1·§3, P2-T1~P2-GATE와 대조하고 확정 문제 5건을 P2-R1~P2-R3으로 전환했다.
  • 왜: 자동 검증 통과와 별개로 보호 route fail-closed, exact API origin, invalid·revoked JWT status, Phase 번호 표시와 완료 문서 추적성이 실제 계약과 일치하는지 독립적으로 판정하기 위해서다.
  • 어떻게:
    • npm run test:run — 성공, 34 files / 130 tests passed.
    • npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx — 성공, 7 files / 20 tests passed.
    • npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, git diff --check, git diff --cached --check — 모두 성공.
    • VITE_API_MODE=mock npm run build:prod — 기대한 거부, exit 1과 VITE_API_MODE=mock is only available during development 확인.
    • npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts — 최초 sandbox listen EPERM, 권한 허용 후 성공, 4 projects / 20 tests passed.
    • npm run e2e:mock -- tests/e2e/mock-mode-boundary.spec.ts — 성공, 4 projects / 4 tests passed.
    • npm run e2e -- tests/e2e/server-mode-boundary.spec.ts — 성공, 4 projects / 12 tests passed. 이 통과 과정에서 404·network error alert가 보호 shell 내부에서 렌더되는 회귀를 별도 확정했다.
    • npm run e2e -- tests/e2e/smoke.spec.ts tests/e2e/auth.spec.ts tests/e2e/accessibility-shell.spec.ts — 성공, 4 projects / 20 tests passed.
    • Vite SSR로 createMockHandlers(createMockStore())를 실행한 재현 — wrong-origin login 200, invalid JWT logout 200, 정상 logout 200, revoked JWT 재logout 200을 확인했다. Vite HMR WebSocket은 sandbox listen EPERM 경고가 있었으나 MSW request 재현 command는 exit 0으로 완료됐다.
  • 남은 항목: REV-P2-001~005를 수정하는 P2-R1~P2-R3. 기존 Phase 2 완료 체크와 검증 기록은 되돌리지 않는다.

Phase 2 구현·Gate 완료 기록 — 2026-07-27

  • 무엇을: explicit server | mock mode, 개발 전용 browser MSW bootstrap, auth preview fixture, mock banner, no-auto-fallback E2E, production mock 차단과 실행 문서를 구현했다.
  • 왜: backend endpoint 구현 전에도 제공 API Contract 범위의 최종 UI를 mock mode에서 확인하되, 기본 server mode와 production build가 mock으로 자동 대체되지 않게 하기 위해서다.
  • 어떻게:
    • P2-T1 focused unit·boundary E2E, P2-T2 auth handler/banner/mock preview E2E, P2-T3 docs/accessibility E2E와 P2-GATE를 2026-07-27 본문 기록대로 실행했다.
    • P2-GATE 기준 npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx는 7 files / 20 tests 통과했다.
    • npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts는 4 browser projects / 20 tests 통과했고 mock login, protected shell, logout 후 재login, 320px·200% zoom, axe critical·serious 0건을 확인했다.
    • npm run typecheck, npm run lint, npm run build:dev, npm run build:prod는 모두 exit 0이었다.
    • Phase 2 코드 리뷰에서 열린 REV-P2-001~005는 이 완료 기록을 삭제하지 않고 P2-R1~P2-R3 회귀 Task로 별도 추적한다.
  • 남은 항목: P2-R1~P2-R3 수정 완료 전에는 Phase 2 리뷰를 닫지 않는다. mock 통과는 후속 도메인 server integration 완료로 간주하지 않는다.

Phase 2 3차 코드 리뷰·QA — 2026-07-27

  • 무엇을: P2-R4P2-R7 수정 뒤의 현재 working tree를 재검토해 REV-P2-010 Low 1건과 REV-P2-011~013 Medium 3건을 확정하고 P2-R8P2-R10 회귀 Task로 전환했다.
  • 왜: 완료 문서가 최종 변경 경로를 추적하는지, Mock Preview banner 이동 뒤 mobile menu의 background inert·responsive breakpoint 전환과 보호 route 오류 recovery가 유지되는지 확인하기 위해서다.
  • 어떻게:
    • npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx — 7 files / 25 tests 통과.
    • npm run test:run — 34 files / 141 tests 통과.
    • npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0.
    • VITE_API_MODE=mock npm run build:prod — 기대대로 exit 1. production 산출물의 worker 파일·mock bootstrap 문자열은 0건.
    • npm run e2e — 4 projects / 32 tests 통과. npm run e2e:mock — 4 projects / 24 tests 통과.
    • Chromium one-off — login과 Character probe 응답이 모두 fromServiceWorker=true임을 확인했다. 320px menu open에서는 banner가 inert 경계 밖에 있었고, 1,200px 전환 뒤 overlay는 숨겨졌지만 main inert·aria-hidden=true가 남았다.
    • 코드·test 대조 — 보호 route 오류 page의 interactive recovery control 0건과 App/server boundary retry test 0건을 확인했다.
    • git diff HEAD --name-only | wc -l — 37개 경로, git diff --check HEAD — exit 0.
    • 문서 반영 검증 — npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts는 1 file / 3 tests 통과, review 상세 ID 13개·Phase 2 회귀 Task 10개를 확인했고 stale 3차 범위 문자열과 문서 whitespace 오류는 0건이었다.
  • 변경 범위: Phase 2 리뷰에 3차 근거·발견·판정을 누적하고 이 문서에 P2-R8~P2-R10만 추가했다. 애플리케이션 코드·test·설정은 변경하지 않았다.
  • 남은 항목: P2-R8 문서 추적성, P2-R9 mobile menu 반응형·inert, P2-R10 보호 route 오류 retry 복구. 세 goal의 대체 검증 또는 RED/GREEN/REFACTOR, 관련 E2E와 P2 Gate가 끝나기 전에는 Phase 2 리뷰를 닫지 않는다.

P2-R9 수정 검증 — 2026-07-27

  • 무엇을: REV-P2-011, REV-P2-012P2-R9 범위에서 수정했다. Mock Preview 성공 shell의 banner를 ProtectedAdminShell background inert container 안으로 옮기고, native matchMedia('(min-width: 1024px)') change에서 mobile menu state와 inert·aria-hidden을 해제하되 숨겨진 mobile trigger로 focus를 복귀하지 않게 했다.
  • 왜: mobile menu가 열린 상태에서 background 전체가 같은 접근성 차단 경계에 속해야 하며, lg 이상 viewport로 전환될 때 보이는 desktop navigation·logout·main이 즉시 다시 조작 가능해야 하기 때문이다.
  • 어떻게:
    • RED unit: npm run test:run -- src/app/App.test.tsx src/shared/ui/__tests__/mock-mode-banner.test.tsx — 2 files 중 App.test.tsx 2 tests가 기대대로 실패했다. 실패 핵심은 mock banner inert background not found모바일 주 메뉴 잔존이다.
    • RED e2e: npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts — 4 browser projects에서 신규 mock shell test가 모두 기대대로 실패했다. 실패 핵심은 320px open state의 bannerInert=false, bannerHidden=false, mainInert=true 불일치다.
    • GREEN unit: 같은 focused unit command — 2 files / 26 tests 통과.
    • GREEN e2e: 같은 mock e2e command — 28 tests 통과. 320px open state에서 banner·main이 같은 inert/aria-hidden 경계에 있고 1,024px·1,200px 전환 뒤 mobile menu가 제거되며 desktop navigation·logout이 보이는 것을 확인했다.
    • Regression: npm run e2e -- tests/e2e/accessibility-shell.spec.ts — 32 tests 통과. npm run typecheck, npm run lint — 모두 exit 0.
    • LSP diagnostics: src/app/App.tsx, src/app/protected-admin-shell.tsx, src/app/App.test.tsx, tests/e2e/mock-preview-shell.spec.ts 모두 0 diagnostics.
    • P2 focused Gate: npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx — P2-R9 문서 기록 반영 전에는 docs contract 1 test가 실패했다. 기록 반영 후 재실행해 7 files / 25 tests 통과했다.
  • 남은 항목: P2-R10 보호 route 오류 retry 복구. mock 통과는 후속 도메인 server integration 완료로 간주하지 않는다.

P2-R10 수정 검증 — 2026-07-27

  • 무엇을: REV-P2-013P2-R10 범위에서 수정했다. 보호 route 오류 page에 native retry button을 추가하고, 현재 session·route visit·retry attempt가 모두 일치할 때만 오류나 검증 성공을 사용하도록 했다. 정상 ApiError의 서버 message는 유지하고 network 오류에는 안전한 공통 안내를 표시한다.
  • 왜: 404·network 오류 뒤에도 사용자가 browser refresh 없이 복구할 수 있어야 하며, 현재 수동 retry가 성공하기 전에는 보호 shell·navigation·logout을 계속 숨겨야 하기 때문이다.
  • 어떻게:
    • RED unit: npm run test:run -- src/app/App.test.tsx — 1 file / 26 tests 중 신규 2 tests가 보호 route 다시 시도 button 부재로 기대대로 실패했고 24 tests는 통과했다.
    • RED E2E: npm run e2e -- tests/e2e/server-mode-boundary.spec.ts — 32 tests 중 신규 retry 시나리오가 4 browser project에서 button 부재로 기대대로 실패했고 28 tests는 통과했다.
    • GREEN unit: 같은 App command — 1 file / 26 tests 통과. 404 없습니다. 보존, network 재실패의 공통 안내·retry 유지, pending 중 shell·logout 비노출과 성공 뒤 shell 표시를 확인했다.
    • GREEN E2E: 같은 server command — 4 projects / 32 tests 통과. 명시적 retry 전후 browser MSW worker 0건과 성공 전 shell·logout 0건을 확인했다.
    • Regression: npm run test:run -- src/shared/config src/shared/mocks src/shared/ui/__tests__/mock-mode-banner.test.tsx — 7 files / 25 tests 통과. npm run test:run — 34 files / 145 tests 통과.
    • Static/build: npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. no-excuse 검사도 변경 TS/TSX 3 files / 위반 0건이었다.
    • LSP diagnostics: src/app 4 files, tests/e2e 6 files에서 diagnostics 0건.
    • 수동·시각 확인: server mode 실제 브라우저 375px·768px·1,280px에서 button 높이 44px, :focus-visible=true, 한국어 clipping·비정상 줄바꿈 0건을 확인했다. retry 성공 뒤 shell·logout 표시와 mock worker 0건을 확인했고 기능 무결성·CJK 정밀 검토가 모두 PASS였다.
    • Diff: git diff --check -- src/app/App.tsx src/app/App.test.tsx tests/e2e/server-mode-boundary.spec.ts docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md — exit 0.
  • 남은 항목: P2-R10 범위 없음. mock 통과는 후속 도메인 server integration 완료로 간주하지 않는다.

Phase 2 4차 코드 리뷰·QA — 2026-07-27

  • 무엇을: P2-R8P2-R10 반영 뒤 working tree 전체 집계, 보호 route pending UI와 mode별 focused E2E 증거를 재검토해 REV-P2-014 Low 1건과 REV-P2-015~016 Medium 2건을 확정하고 P2-R11P2-R13으로 전환했다.
  • 왜: 완료 판정이 untracked 파일, 300ms 이상 retry 대기 상태와 Task가 지정한 단일 E2E spec의 실제 수집 범위를 빠뜨리지 않는지 확인하기 위해서다.
  • 어떻게:
    • npm run test:run -- src/app/App.test.tsx src/shared/ui/__tests__/mock-mode-banner.test.tsx — 2 files / 28 tests 통과. npm run test:run — 34 files / 145 tests 통과.
    • npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0, build는 각 160 modules 변환.
    • VITE_API_MODE=mock npm run build:prod — 기대대로 exit 1. production worker 파일과 mock bootstrap 문자열은 0건.
    • npm run e2e — 4 projects / 32 tests 통과. npm run e2e:mock — 4 projects / 28 tests 통과.
    • git diff HEAD --name-only | wc -l — tracked 37개, git status --porcelain=v1 | wc -l — 전체 38개, git ls-files --others --exclude-standardsrc/app/protected-admin-shell.tsx 1개.
    • npm run e2e -- --list tests/e2e/server-mode-boundary.spec.ts — 4 files / 32 tests, 직접 VITE_API_MODE=server npx playwright test tests/e2e/server-mode-boundary.spec.ts --list — 1 file / 12 tests. 고정 script가 focused filter를 무효화하고 P2-R10 기대 16 tests 미달을 가리는 것을 확인했다.
    • Chromium one-off — 404 retry 성공 응답을 350ms 지연했을 때 #root child·main·status·alert 0개, active element BODY; 응답 뒤 shell·logout 표시를 확인했다. 최초 sandbox local listen·browser launch 실패는 권한 허용 재실행으로 보완했다.
    • 문서 반영 검증 — npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts는 1 file / 3 tests 통과. review 상세 ID 16개, Phase 2 회귀 Task 13개와 문서 whitespace 오류 0건을 확인했다.
  • 변경 범위: Phase 2 리뷰에 4차 근거·발견·판정을 누적하고 이 문서에 P2-R11~P2-R13만 추가했다. 애플리케이션 코드·test·설정은 변경하지 않았다.
  • 남은 항목: P2-R11 working tree 추적성, P2-R12 보호 route pending 피드백, P2-R13 focused E2E filter·network retry 증거. 세 goal의 대체 검증 또는 RED/GREEN/REFACTOR와 관련 Gate가 끝나기 전에는 Phase 2 리뷰를 닫지 않는다.

P2-R11~P2-R13 수정 검증 — 2026-07-27

  • 무엇을: REV-P2-014~016P2-R11~P2-R13 범위에서 수정했다. working tree 범위는 tracked 37개 + untracked src/app/protected-admin-shell.tsx 1개로 구분해 기록하고, 보호 route pending에는 기존 PageState loading status를 표시했다. E2E script의 spec allowlist는 playwright.config.ts testMatch로 옮기고 network retry 재실패 E2E를 추가했다.
  • 왜: 완료 증거가 untracked 파일, 접근 가능한 300ms 이상 pending feedback, focused E2E file filter와 실제 network retry 경계를 빠뜨리지 않게 하기 위해서다.
  • 어떻게:
    • P2-R11 대체 검증: git diff HEAD --name-only | wc -l — tracked 37개, git status --short --untracked-files=all | wc -l — 전체 38개, git ls-files --others --exclude-standardsrc/app/protected-admin-shell.tsx 1개.
    • P2-R12 RED: npm run test:run -- src/app/App.test.tsx — 최초 pending 전용 test 1건과 기존 404 retry test의 pending assertion이 role="status" 부재로 기대대로 실패했다.
    • P2-R12 GREEN: 같은 command — 1 file / 27 tests 통과. 최초 probe와 retry pending 중 role="status", 보호 main·logout 0건을 확인했다.
    • P2-R13 RED: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts — npm script와 Playwright config contract 2 tests가 기대대로 실패했다.
    • P2-R13 GREEN focused: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 6 tests 통과. npm run e2e -- --list tests/e2e/server-mode-boundary.spec.ts — 1 file / 16 tests, npm run e2e:mock -- --list tests/e2e/mock-preview-shell.spec.ts — 1 file / 24 tests.
    • Surface: npm run e2e -- tests/e2e/server-mode-boundary.spec.ts — 16 tests 통과. npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts — 24 tests 통과.
    • LSP diagnostics: src/app/App.tsx, src/app/App.test.tsx, playwright.config.ts, src/shared/mocks/__tests__/mode-boundary.test.ts 0 diagnostics. tests/e2e/server-mode-boundary.spec.ts 단일 fresh diagnostics는 timeout이었고 focused E2E와 typecheck로 보완한다.
    • Final Gate: npm run test:run — 34 files / 147 tests 통과. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. npm run e2e — 36 tests 통과, npm run e2e:mock — 28 tests 통과.
    • Diff: git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md tests/e2e/server-mode-boundary.spec.ts package.json playwright.config.ts README.md docs/agent-guide/scripts.md src/app/App.tsx src/app/App.test.tsx src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — exit 0.
  • 남은 항목: P2-R11~P2-R13 범위 없음. mock 통과는 후속 도메인 server integration 완료로 간주하지 않는다.

Phase 2 5차 코드 리뷰·QA — 2026-07-27

  • 무엇을: P2-R11~P2-R13 반영 뒤 실제 test case 구조·실행 수와 plan/review의 완료 상태를 대조해 REV-P2-017 Low 1건을 확정하고 P2-R14 회귀 Task로 전환했다.
  • 왜: 동작 검증 통과와 별개로 TDD 완료 체크·검증 수치가 실제 구현과 같은 사실을 표시하는지 확인하기 위해서다.
  • 어떻게:
    • npm run test:run -- src/app/App.test.tsx — 1 file / 27 tests 통과. rg -c '^test\(' src/app/App.test.tsx — 27건.
    • 코드 대조 — 최초 probe pending 전용 test 1건을 추가했고 retry pending은 기존 404 retry test에 assertion을 보강했음을 확인했다. P2-R12의 두 신규 test·App 28 tests 이상 완료 체크와 불일치한다.
    • npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — 2 files / 6 tests 통과. npm run test:run — 34 files / 147 tests 통과.
    • npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0. VITE_API_MODE=mock npm run build:prod — 기대한 production guard로 exit 1.
    • focused E2E list는 server 1 file / 16 tests, mock 1 file / 24 tests. 최초 sandbox listen EPERM 뒤 허용된 로컬 실행에서 server boundary 16, mock preview 24, bare server 36, bare mock 28 tests가 모두 통과했다.
    • working tree는 tracked diff 37개 + untracked src/app/protected-admin-shell.tsx 1개 = 전체 38개 항목으로 유지됐다.
  • 변경 범위: Phase 2 리뷰REV-P2-017과 5차 근거·판정을 누적하고 이 문서에 P2-R14만 추가했다. 애플리케이션 코드·test·설정은 변경하지 않았다.
  • 남은 항목: P2-R14 범위 없음. mock 통과는 후속 도메인 server integration 완료로 간주하지 않는다.

P2-R14 수정 검증 — 2026-07-27

  • 무엇을: REV-P2-017P2-R14 범위에서 수정했다. P2-R12 완료 증거를 실제 App test 구조인 최초 pending 전용 test 1건 + 기존 404 retry test의 pending assertion 보강 + App 27 tests로 정정했다.
  • 왜: 문서가 “두 신규 test·28 tests 이상”을 완료 조건처럼 표시하면 후속 reviewer가 실제 test case 수와 검증 시나리오 수를 혼동하기 때문이다.
  • 어떻게:
    • 대체 RED: rg -c '^test\(' src/app/App.test.tsx — 27건. rg -n 'initial protected route probe is pending|retries a protected route 404' src/app/App.test.tsx — 최초 pending 전용 test와 기존 404 retry test 위치를 확인했다.
    • GREEN docs: P2-R12 TDD 절차·기대 결과, P2-R11~P2-R13 수정 검증 기록, review 요약·종료 판정을 실제 구조와 일치시켰다.
    • Focused: npm run test:run -- src/app/App.test.tsx — 1 file / 27 tests 통과.
    • 문서 검증: stale 신규 pending status tests 2건 검색 결과 0건, git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md — exit 0.
  • 남은 항목: Phase 2 review의 열린 확정 항목 없음. 애플리케이션 코드·test·설정은 변경하지 않았다.

Phase 2 6차 코드 리뷰·QA — 2026-07-27

  • 무엇을: P2-R14 반영 뒤 Phase 2 구현·test·production·mode별 E2E와 review 현재 상태를 다시 대조해 REV-P2-018 Low 1건을 확정하고 P2-R15로 전환했다.
  • 왜: review 요약·상세·종료 판정과 plan 전환 절이 완료된 회귀 Task의 현재 상태를 같은 의미로 표시하는지 확인하기 위해서다.
  • 어떻게:
    • npm run test:run — 34 files / 147 tests 통과.
    • npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0, build는 각각 160 modules 변환.
    • VITE_API_MODE=mock npm run build:prod — 기대한 production guard로 exit 1.
    • npm run e2e — 최초 sandbox listen EPERM 뒤 로컬 실행 권한으로 재실행해 4 projects / 36 tests 통과.
    • npm run e2e:mock — 최초 sandbox listen EPERM 뒤 로컬 실행 권한으로 재실행해 4 projects / 28 tests 통과.
    • Chromium one-off — login·보호 route 응답 fromServiceWorker=true, 320px menu·logout 높이 60px, mobile link 선택 뒤 menu·background inert 잔존 0건을 확인했다.
    • git diff --check HEAD — review 문서 반영 전 exit 0. review §7의 P2-R8~P2-R13 “아직 수정하지 않았다” 2건과 §5·§6·§8의 수정 완료 상태가 모순됨을 확인했다.
    • 문서 반영 후 npm run test:run -- src/shared/mocks/__tests__/mock-preview-docs.test.ts — 1 file / 3 tests 통과, plan/review 대상 git diff --check — exit 0.
  • 변경 범위: 이 review 문서와 plan-task.mdREV-P2-018, P2-R15 판정·후속 계획만 추가했다. 애플리케이션 코드·test·설정은 변경하지 않았다.
  • 남은 항목: P2-R15에서 review §7의 현재 상태 문구를 정정하고 문서 검색·diff 검증을 누적해야 한다.

Phase 2 7차 코드 리뷰·QA — 2026-07-27

  • 무엇을: P2-R15 반영 뒤 Phase 2 상단 현재 상태, Task 본문, review 종료 판정과 하단 최신 Progress를 대조해 REV-P2-019 Low 1건을 확정하고 P2-R16으로 전환했다.
  • 왜: 완료된 회귀 Task의 inline 기록뿐 아니라 Phase 현재 상태와 하단 누적 Progress도 Phase 3 실행자가 같은 완료 상태로 해석할 수 있어야 하기 때문이다.
  • 어떻게:
    • 대체 RED 완료 상태 검색 — Phase 2 상단의 P2-R15까지 완료·Phase 3 진행 가능 exact 상태가 없어 exit 1.
    • 대체 RED 최신 Progress 검색 — 문서 끝이 6차 재검증의 P2-R15 수정 필요 상태로 끝나 현재 완료 기록이 없어 exit 1.
    • 코드 회귀 기준은 6차 재검증 직후 독립 확인에서 unit 147 tests, server E2E 36 tests, mock E2E 28 tests, typecheck·lint·dev/prod build 통과와 production mock guard 거부를 확인했다.
  • 변경 범위: 이 review 문서와 plan-task.mdREV-P2-019, P2-R16 판정·후속 계획만 추가했다. 애플리케이션 코드·test·설정은 변경하지 않았다.
  • 남은 항목: P2-R16에서 Phase 2 현재 상태와 최신 Progress를 정정하고 문서·Phase 2 Gate 검증을 누적해야 한다.

P2-R16 수정 검증 — 2026-07-27

  • 무엇을: REV-P2-019를 수정했다. Phase 2 상단 현재 상태를 P2-R16까지 완료 및 Phase 3 진행 가능으로 갱신하고, 6차 당시 남은 항목을 보존한 채 최신 수정 검증을 누적했다.
  • 왜: Phase 3 실행자가 완료된 P2-R15를 열린 선행 작업으로 오해하지 않고 Phase 2의 실제 완료 상태를 단일하게 판정할 수 있어야 하기 때문이다.
  • 어떻게:
    • 대체 GREEN: Phase 2 완료 상태 exact 검색과 최신 Progress 검색이 각각 1건 이상 일치했다. 6차 당시 P2-R15 남은 항목도 이력으로 보존됐다.
    • Full unit/static/build: npm run test:run — 34 files / 147 tests 통과. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod — 모두 exit 0, build는 각각 160 modules 변환.
    • Production boundary: VITE_API_MODE=mock npm run build:prod — 기대대로 exit 1. production worker 파일과 mock bootstrap 문자열은 0건.
    • E2E: npm run e2e — 4 projects / 36 tests 통과. npm run e2e:mock — 4 projects / 28 tests 통과.
    • Diff: git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/review-phase-2.md — exit 0.
  • 남은 항목: Phase 2 review의 열린 확정 항목 없음. P2-GATE와 모든 회귀 수정이 완료돼 Phase 3 진행 가능. mock 통과는 Phase 3의 실제 server integration 완료로 간주하지 않는다.

OpenAPI 계약 교체 및 Phase 3~9 문서 정합화 — 2026-07-28

  • 무엇을: 삭제된 Markdown 계약을 api-contract.openapi.json으로 교체한 사실을 PRD와 계획에 반영하고, Phase 3~9의 query·multipart part·request/response DTO·오류·외부 의존·TDD/검증 계약을 새 OpenAPI에 맞췄다. 가이드·샘플은 실제 API Contract 형식을 사용하도록 일반화하고 과거 review에는 계약 이력 안내만 추가했다.
  • 왜: 삭제된 계약 링크와 기존 DTO 가정을 그대로 두면 Phase 3 이후 구현자가 releaseDateUtc, seriesIds, {contentIds}, {seriesIds}, Community 상세, FanTalk 수정과 댓글 CRUD처럼 OpenAPI에 없는 계약을 추정하게 되기 때문이다.
  • 어떻게:
    • OpenAPI parse·구조 검사 — openapi=3.1.0 version=2.0.0 paths=15 operations=23.
    • schema assertion — Character 필수 image/systemPrompt, Audio contentFile/releaseDate/themeId, Series keyword/contentIdList/ids, Community postImage/audioFile, FanTalk pagination field를 확인해 schema assertions=passed.
    • 요구사항 ID 검사 — AUTH 13, CHAR 18, AUDIO 33, SERIES 18, COMMUNITY 15, FANTALK 11, COMMENT 6, FILE 15, MOCK 9개이며 중복 0건. 백엔드 외부 의존은 EXT-001~011로 추적한다.
    • Phase 계획 검사 — Phase 3~9의 Task 실행 계약 7개와 Gate 수동 확인 7개를 확인했다.
    • Markdown link 검사 — 대상 11개 문서의 상대 링크가 모두 존재했다.
    • stale 활성 링크 검사 — PRD·plan·가이드·샘플의 삭제된 api-contract.md 링크 0건. 과거 review와 검증 기록의 표기는 계약 이력 안내 아래 역사 근거로 보존했다.
    • 회귀 검사 — npm run test:run 34 files / 147 tests 통과, npm run typechecknpm run lint 모두 출력 없이 exit 0.
    • 변경 범위 검사 — 애플리케이션 source·설정 diff 0건이며 git diff --check는 출력 없이 exit 0.
  • 문서 위치 정정: 기능별 설계·실행 문서를 docs/superpowers/에 따로 두지 않고 설계 결정은 이 디렉터리의 prd.md, 실행 계약과 검증 기록은 plan-task.md에 통합했다. docs/agent-guide/documentation.md에도 같은 배치 규칙을 명시했다.
  • 남은 항목: 추가 사용자 결정은 없다. backend가 제공해야 하는 original work·genre lookup, active-only 보장, Community pagination 종료 metadata, FanTalk 상세·수정·filter/sort· 유일성 오류, Comments CRUD·권한 오류와 도메인별 오류 key는 Phase별 외부 의존으로 남는다.

EXT-006 인증 계약 기록·제공 범위 우선 실행 결정 — 2026-07-28

  • 무엇을: EXT-006에 현재 구현된 로그인·로그아웃 endpoint, header, body, 성공 data, client 처리와 검증 근거를 기록했다. Phase 3부터 제공된 OpenAPI 범위를 먼저 구현해 Phase 9 활성 범위 Gate까지 진행하고 미제공 계약은 후속 vertical slice로 보완하는 실행 전략을 확정했다.
  • 왜: 이미 동작하는 인증을 구현 대기로 오해하지 않게 하고, backend가 정식 OpenAPI를 작성할 때 현재 프론트엔드 계약을 바로 대조할 수 있게 하며, 독립 기능을 계약 대기 때문에 직렬로 지연하지 않기 위해서다.
  • 어떻게:
    • 구현 대조 — src/features/auth/api/auth-api.ts, src/features/auth/model/auth-session.tsx, auth contract/session/mock test와 tests/e2e/auth.spec.ts에서 두 endpoint와 client 동작을 확인했다.
    • 문서 구조 검사 — EXT-006 endpoint·비차단 상태, Phase 3~9 진행 문구, P8-GATE·P9-GATE 후속 규칙, EXT-001~011, Markdown link·fence를 검사해 모두 통과했다.
    • focused unit — npm run test:run -- src/features/auth/tests/auth-api.test.ts src/features/auth/tests/auth-session.test.tsx src/shared/mocks/__tests__/auth-handlers.test.ts는 3 files / 26 tests 통과.
    • 인증 E2E — npm run e2e -- tests/e2e/auth.spec.ts는 최초 sandbox local listen EPERM으로 실패했고, 포트 권한을 허용한 동일 명령 재실행에서 4 browser projects / 4 tests 통과.
    • git diff --check — 출력 없이 exit 0.
  • 변경 범위: prd.md, plan-task.md만 보완했으며 api-contract.openapi.json과 애플리케이션 코드·test·설정은 변경하지 않았다.
  • 남은 항목: backend가 EXT-006 두 operation을 정식 OpenAPI에 포함하거나 별도 version 계약으로 고정하면 현재 기록과 대조해 정규화한다. 다른 외부 의존은 제공 범위 구현을 차단하지 않고 각 후속 vertical slice에서 보완한다.

P3-T1 Character 계약 확인 및 실행 범위 확정, 2026-07-28

  • 무엇을: OpenAPI Character 4개 operation과 schema, PRD CHAR-001~018, MOCK-001~009, 외부 의존 EXT-001, EXT-007, EXT-011을 대조해 P3-T1 체크박스 5개를 완료로 표시했다. PRD는 이미 같은 범위를 반영하고 있어 수정하지 않았다.
  • 왜: P3-T2P3-T3가 계약에 없는 original work lookup, Character 전용 오류 message key, 검색 결과 active-only 보장을 추정하지 않게 하기 위해서다.
  • 어떻게:
    • JSON parse: node -e "JSON.parse(require('fs').readFileSync('docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json','utf8'))"는 출력 없이 exit 0으로 OpenAPI JSON 파싱을 확인했다.
    • Character 계약 확인: focused node -e는 exit 0으로 listAiCharacters, createAiCharacter, getAiCharacter, updateAiCharacter, 목록 searchTerm/page/size, data.totalCount/content, item id, 상세 characterUUID/originalWork, create/update 성공 data=null, multipart image/request, 공통 오류 ApiErrorResponse, original work endpoint 0건을 확인했다.
    • 문서 추적: rg -n 'CHAR-0(1[2-8]|0[1-9])|EXT-001|EXT-007|EXT-011|originalWork' docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md는 exit 0으로 PRD와 plan의 관련 범위를 확인했다.
    • Diff 검사: git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/prd.md는 출력 없이 exit 0으로 문서 공백 오류 0건을 확인했다.
  • 남은 항목: original work lookup, Character 검색 결과 active-only 보장, Character 전용 오류 message key는 backend 제공 대기다. P3-T1에서는 애플리케이션 코드, route, component, test, handler, fixture를 만들지 않았다.

P3-T2 Character 목록·선택·workspace 구현, 2026-07-28

  • 무엇을: Character 목록·검색·선택과 /ai-characters/:characterId 상세 workspace 복원을 구현하고, P3-T2 체크박스 8개를 완료로 표시했다. 생성·수정·비활성화 form과 mutation은 P3-T3 범위로 남겼다.
  • 왜: Phase 3 workspace core가 URL의 characterId로 후속 Audio·Series· Community·FanTalk 하위 리소스를 격리해야 하기 때문이다.
  • 어떻게:
    • RED: npm run test:run -- src/features/characters/tests/character-list.test.tsx src/layouts/CharacterWorkspaceLayout.test.tsx에서 2 files / 7 tests가 모두 실패해 목록·workspace 미구현을 확인했다.
    • GREEN/REFACTOR: 같은 focused 명령은 이후 2 files / 7 tests 통과했고, 회귀 포함 npm run test:run -- src/shared/ui/__tests__/search-toolbar.test.tsx src/app/App.test.tsx src/features/characters/tests/character-list.test.tsx src/layouts/CharacterWorkspaceLayout.test.tsx src/shared/mocks/__tests__/auth-handlers.test.ts는 5 files / 46 tests 통과했다.
    • Type/lint/build: npm run typecheck, npm run lint, npm run build가 모두 exit 0이었다.
    • LSP: src/app, src/features/characters, src/layouts, src/shared/ui, src/shared/mocks 진단은 오류 0건이었다.
    • 수동 QA(mock): npm run dev:mockhttp://127.0.0.1:8889에서 Playwright로 로그인 → 목록 루나/테오 표시 → settled 후 Character GET 0건 → 검색 search=루나 URL 반영 → 루나 선택 /ai-characters/101character-uuid-101·원작 달빛 상담소 표시 → /ai-characters/202 deep link → read-only 배너 표시와 저장/비활성화 버튼 0건을 확인했다. 브라우저 콘솔 error/warning은 0건이었다.
  • 남은 항목: Character 생성·수정·soft delete, original work lookup, active-only server 보장, Character 전용 오류 message key는 P3-T3 또는 backend 제공 후속 범위다.

P3-T3 Character 생성·수정·soft delete 부분 구현, 2026-07-28

  • 무엇을: Character create/update/deactivate API serializer, 생성·수정 route와 form, soft delete dialog, image crop 적용, mock store mutation을 구현하고 검증된 P3-T3 체크박스 중 계약 추정 없이 검증된 항목만 완료로 표시했다.
  • 왜: OpenAPI가 제공한 multipart create/update와 data=null 성공 계약을 UI와 mock server가 같은 request/response shape로 사용하게 하기 위해서다.
  • 어떻게:
    • RED/GREEN: create/update/deactivate serializer, 생성 form 필수값·중복 제출· crop 적용, 수정 form region read-only·dirty leave guard·교체 이미지 crop, 상세 soft delete dialog, mock create/update/deactivate store 갱신 test를 실패 확인 후 최소 구현으로 통과시켰다. 추가로 crop 취소 시 replacement image 미전송과 기존 image 삭제 UI 부재, characterUUID 읽기 전용 표시와 externalCharacterId 미노출 test를 RED/GREEN으로 확인했다. optional scalar와 repeated fields는 create 배열, update 배열 null, nullable scalar 직렬화 test를 추가했고, npm run typecheck RED에서 update 반복 필드 null 타입 미허용 오류 8건을 확인한 뒤 create/update 요청 타입을 계약에 맞게 분리했다.
    • Focused 검증: npm run test:run -- src/features/characters/tests/character-api.test.ts src/features/characters/tests/CharacterCreatePage.test.tsx src/features/characters/tests/CharacterEditPage.test.tsx src/features/characters/tests/CharacterDetailPage.test.tsx src/features/characters/tests/character-mutation-reload.test.tsx src/shared/mocks/__tests__/character-handlers.test.ts src/shared/mocks/__tests__/auth-handlers.test.ts src/app/App.test.tsx는 8 files / 57 tests 통과했다.
    • 추가 재조회·성공 알림 검증: src/features/characters/tests/character-mutation-reload.test.tsx로 create 성공 후 목록 route 재진입·목록 재조회와 성공 알림, update 성공 후 상세 route 재진입·상세 재조회와 성공 알림, soft delete 성공 후 목록 route 이동·목록 재조회와 성공 알림을 3 tests 통과로 확인했다. Character 화면은 현재 React Query cache를 사용하지 않아 cache invalidation 대신 navigateTo() 기반 route 재진입으로 직접 요청을 다시 발생시키는 정책으로 정정했다.
    • Type/lint/build: npm run typecheck, npm run lint, npm run build, git diff --check가 모두 exit 0이었다.
    • 전체 회귀 검증: npm run test:run은 42 files / 175 tests 통과했다. 병렬 재실행 중 CharacterWorkspaceLayout.test.tsxcharacter-uuid-101 중복 표시 assertion이 먼저 실패했으나, 헤더와 프로필 양쪽 표시가 의도된 상태임을 확인하고 getAllByText(...).toHaveLength(2)로 좁힌 뒤 npm run test:run -- src/layouts/CharacterWorkspaceLayout.test.tsx 1 file / 5 tests와 전체 42 files / 175 tests를 통과시켰다.
    • LSP: CharacterCreatePage.test.tsxCharacterEditPage.test.tsx는 진단 0건이었다. 일부 TSX 파일은 LSP fresh diagnostics가 timeout 됐고, Markdown은 LSP server 미설정으로 진단 불가했다. npm run typecheck로 타입 오류 0건을 확인했다.
    • 수동 QA(mock): npm run dev:mockhttp://127.0.0.1:8889에서 Playwright로 로그인 → /ai-characters/new 생성 form 진입 → PNG 업로드 후 이미지 crop dialog와 예상 결과 800 × 800px 확인 → 적용노아 생성 → 목록 총 3개와 노아 선택 확인 → /ai-characters/1000 상세 진입 → 수정 화면에서 지역 input disabled와 KR 확인 → 상세 복귀 → 노아 비활성화 확인 dialog의 복원 미지원·hard delete 미지원 문구 확인 → 비활성화 확인/ai-characters 목록 복귀와 총 2개, 노아 제외를 확인했다. 브라우저 console은 기능 error/warning 0건이었고, favicon 404만 별도 관찰됐다. 추가로 /ai-characters/101에서 캐릭터 UUIDcharacter-uuid-101 표시, character-101 미노출, 브라우저 console warning/error 0건을 확인했다.
    • 추가 수동 QA(mock): 같은 mock 서버에서 Playwright로 create 성공 후 /ai-charactersAI 캐릭터를 생성했습니다. status, update 성공 후 /ai-characters/101AI 캐릭터를 저장했습니다. status, soft delete 성공 후 /ai-charactersAI 캐릭터를 비활성화했습니다. status를 확인했다. 생성 form의 name, systemPrompt, description에는 maxlength가 없고, 반복 필드 입력 UI는 아직 노출되지 않음을 확인했다. OQ-009 권고안은 backend 호환 전까지 client 상한을 추가하지 않고, backend가 길이·개수 계약을 제공하면 name, systemPrompt, descriptionmaxlength와 tags·hobbies·values·goals·relationships· personalities·backgrounds·memories의 최대 개수를 PRD·OpenAPI·schema·경계 test에 같은 변경으로 반영하는 것이다.
    • Visual QA: p3-t3-success-notification.png 캡처를 기준으로 독립 Oracle 2건이 모두 PASS했다. Pass A는 SuccessNotificationDESIGN.md와 CSS token 기반의 실제 DOM 구현이며 create/update/deactivate 경로가 모두 success state를 전달한다고 판정했다. Pass B는 AI 캐릭터를 비활성화했습니다. 알림의 CJK 줄바꿈·클리핑·대비· 시각 계층 문제가 없다고 판정했다.
  • 남은 항목: original work combobox는 계약 미제공으로 아직 완료 표시하지 않았다.

P3-T4 Character 반응형·접근성 검증, 2026-07-28

  • 무엇을: Character workspace의 desktop/tablet 관리 action, mobile 조회 전용 capability, mobile Card 접근 이름, keyboard-only 흐름, 320/768/1280px·200% zoom·axe 기준을 검증하고 P3-T4 체크박스 5개를 완료로 표시했다.
  • 왜: Phase 3 활성 범위가 viewport별 route/action policy와 접근성 기준을 충족하는지 P3-GATE 전에 독립적으로 고정하기 위해서다.
  • 어떻게:
    • RED: 초기 focused E2E tests/e2e/character-workspace.spec.ts는 10 tests를 수집했고, 의도한 반응형·접근성 공백으로 4 passed / 6 failed를 기록했다.
    • GREEN/product: 이후 focused E2E는 10 passed에 도달했고, typecheck·lint·design-system focused test도 통과했다.
    • Review gap: Oracle이 create/edit direct route와 dirty-leave dialog의 axe·zoom coverage 누락을 발견했고, test-only patch로 해당 coverage를 확장했다.
    • Final fix: 320px mobile direct create/edit guidance route에서도 계속 진행 전에 200% zoom, horizontal overflow 없음, axe critical·serious 0건을 확인하게 했다.
    • Final verification: npm run e2e:mock -- tests/e2e/character-workspace.spec.ts --project=chromium -> exit 0, 11 passed (31.3s).
    • Static verification: npm run typecheck -> exit 0. npm run lint -> exit 0.
    • LSP diagnostics: tests/e2e/character-workspace.spec.ts fresh diagnostics는 timeout됐고, TypeScript 정확성은 npm run typecheck로 보완했다.
    • Oracle re-check: 현재 source inspection 기준 prior blocker는 SPEC_PASS 판정이었다.
  • 남은 항목: original work combobox는 계약 미제공으로 계속 제외한다. P3-GATE는 아직 남아 있다.

P3-GATE Character 통합 검증, 2026-07-28

  • 무엇을: Phase 3 Character 활성 범위의 unit/component, mock E2E, server-mode allowlist E2E, typecheck, lint, production build를 실행하고 P3-GATE를 통과로 판정했다.
  • 왜: mock UI journey와 실제 server mode 경계 검증을 분리해 기록하고, P3-T1~P3-T4가 후속 Phase의 workspace 기반으로 안전한지 확인하기 위해서다.
  • 어떻게:
    • 원인 조사: npm run e2e:mock -- tests/e2e/character-workspace.spec.ts는 최초 44 tests 중 3 failures를 재현했다. 실패 원인은 앱 버그가 아니라 E2E 범위 오류였다. mobile 프로젝트는 PRD 정책상 Character mutation action을 숨기는데 keyboard-only edit flow가 수정 버튼을 기다렸고, WebKit/Safari는 기본 설정상 Tab이 link를 일관되게 focus하지 않는다. Apple Safari keyboard shortcut 문서와 Playwright WebKit 이슈 조사도 같은 결론이었다.
    • E2E 정정: tests/e2e/character-workspace.spec.ts의 keyboard-only edit/dirty-leave flow는 desktop Chromium 전용 capability로 명시하고, mobile/WebKit project는 platform policy 사유로 skip하게 했다. mobile 조회·검색·상세, direct create/edit desktop 안내, Card accessible name, 320/768/1280px·200% zoom·axe 검증은 같은 spec의 별도 tests로 유지된다.
    • Gate 명령 정정: npm run e2e -- tests/e2e/character-workspace.spec.tsplaywright.config.ts server mode testMatch와 교집합이 없어 No tests found가 나는 잘못된 focused 명령이었다. P2-R7/P2-R13의 mode allowlist 계약에 맞춰 P3-GATE server mode 명령을 bare npm run e2e로 정정했다.
    • Unit/component: npm run test:run -- src/features/characters src/layouts/CharacterWorkspaceLayout.test.tsx — 7 files / 24 tests 통과.
    • Mock E2E: npm run e2e:mock -- tests/e2e/character-workspace.spec.ts — 44 tests 중 41 passed, 3 skipped, exit 0. skipped 3건은 mobile/WebKit keyboard edit flow platform policy 사유다.
    • Server E2E: npm run e2e — server allowlist 4 specs / 36 tests 통과. Character server integration은 original work lookup, active-only 보장, Character 전용 오류 key가 backend 계약 대기라 mock UI 완료와 별도 상태로 유지한다.
    • Static/build: npm run typecheck, npm run lint, npm run build가 모두 exit 0이었다.
    • LSP/LOC: tests/e2e/character-workspace.spec.ts fresh LSP diagnostics는 timeout됐고, Markdown은 LSP server 미설정이다. TypeScript 정확성은 npm run typecheck로 보완했다. 변경 후 tests/e2e/character-workspace.spec.ts는 순수 LOC 185줄이다.
  • 남은 항목: original work combobox, Character active-only server 보장, Character 전용 오류 message key는 backend 계약 제공 대기다. Phase 3의 mock UI journey와 server mode 경계 검증은 완료됐으며, 실제 Character server integration 완료로 과대표현하지 않는다.

P4-T1 Audio 계약 확인 및 실행 범위 확정, 2026-07-28

  • 무엇을: OpenAPI Audio 5개 operation과 AudioContent*, AudioContentTheme, 공통 오류 schema를 PRD AUDIO-001~033, FILE-001~002, FILE-006~009, FILE-012~013, MOCK-001~009, PRD 9의 Audio 반응형 범위와 대조해 P4-T1 체크박스 7개를 완료로 표시했다. PRD와 OpenAPI는 이미 같은 범위를 반영하고 있어 수정하지 않았다.
  • 왜: P4-T2P4-T3가 status query/status field, seriesIds, 수정 content file 교체, Audio 전용 오류 key, price 상한, active-only 보장, backend container/codec 검증을 추정하지 않게 하기 위해서다.
  • 어떻게:
    • JSON parse: node -e "JSON.parse(require('fs').readFileSync('docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json','utf8'))"는 출력 없이 exit 0으로 OpenAPI JSON 파싱을 확인했다.
    • Audio 계약 확인: focused node 검사는 exit 0으로 GET /api/v2/admin/ai-characters/audio-content-themes, GET/POST /api/v2/admin/ai-characters/{characterId}/audio-contents, GET/PUT /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}listAiCharacterAudioContentThemes, listAiCharacterAudioContents, createAiCharacterAudioContent, getAiCharacterAudioContent, updateAiCharacterAudioContent operation을 확인했다. theme는 query/body 없이 data[]id, theme, image를 사용한다.
    • Contract fixture: 공통 오류는 ApiErrorResponsesuccess=false, message, data=null, errorProperty와 status 400/401/403/404/405/406/415/500만 사용한다. Audio 전용 message key, backend container/codec 오류 계약, price 최대값, active-only 보장은 미제공이므로 fixture로 추정하지 않는다.
    • 요청·응답 고정: 목록은 search_word, page, sizedata.totalCount, data.items[]를 사용한다. 상세는 필수 timezone=Asia/Seoul query를 보낸다. 생성 multipart는 필수 contentFile, coverImage, request와 성공 data.contentId를 사용한다. 수정 multipart는 필수 request와 optional coverImage만 있고 성공은 data=null이다.
    • 제외 고정: 목록 query와 목록·상세 schema에 status가 없고 생성·수정 request에 seriesIds가 없으므로 client status filter, status enum, series 연결 UI, 수정 content file 교체를 만들지 않는다. price는 PRD의 0 이상 정수 “캔”만 유지하고 상한 test는 계약 제공 후 추가한다.
    • 상태·component map: 목록 Page는 characterId, search_word, page, size route/query와 loading·empty·error·retry를 가진다. 상세 Page는 characterId, contentId, timezone과 단일 재생·media error 안내를 가진다. form/upload Page는 desktop/tablet에서만 create/update/deactivate/upload action을 제공하고 mobile은 PRD 9에 따라 조회·재생만 제공한다. Feature component는 Audio releaseDate/theme/file/price 규칙을 소유하고, file input/crop/player/page state는 Phase 1 shared contract를 사용한다.
    • Mock scenario: deterministic seed는 목록 2건 이상, 상세 1건 이상, theme 2건 이상을 둔다. browser handler는 제공 계약 범위의 theme list, list/search/page, detail with timezone, create multipart 성공 contentId, update/soft delete 성공 data=null, upload progress/cancel/retry happy path와 공통 오류만 제공한다. Audio 전용 오류 key, active-only server 결과, backend codec 판정, price 상한은 mock에서도 추정하지 않는다.
    • 문서 추적: rg -n 'AUDIO-0(0[1-9]|[12][0-9]|3[0-3])|FILE-00[126789]|FILE-01[23]|MOCK-00[1-9]|오디오 목록·상세·재생|오디오 생성·수정·비활성화·업로드|EXT-00[79]|EXT-010|EXT-011' docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md는 exit 0으로 PRD와 plan의 Audio, File, Mock, 외부 의존 범위를 확인했다.
    • Diff 검사: git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md는 출력 없이 exit 0으로 문서 공백 오류 0건을 확인했다.
  • 남은 항목: Audio 전용 오류 message key, backend container/codec 검증 계약, price 최대값, active-only 반환 보장은 backend 제공 대기다. P4-T1에서는 애플리케이션 코드, route, component, test, handler, fixture를 만들지 않았다.

P4-T2 Audio 목록·상세·player 구현, 2026-07-28

  • 무엇을: 선택 Character context의 Audio 목록·상세·player vertical slice를 구현하고 P4-T2 체크박스 9개를 완료로 표시했다.
  • 왜: AUDIO-021, AUDIO-026, AUDIO-029, AUDIO-030의 검색·상세 timezone·재생·media error 안전 동작을 P4-T3 form/upload 전에 고정하기 위해서다.
  • 어떻게:
    • RED: npm run test:run -- src/features/audio-contents/tests/audio-list.test.tsx src/features/audio-contents/tests/audio-player.test.tsx는 최초 실행에서 AudioContentDetailPage import 부재와 nested audio route 미지원으로 실패했다. mock handler focused RED도 audio endpoint unhandled request로 실패했다.
    • GREEN: 같은 focused command는 2 files / 2 tests 통과했고, src/shared/mocks/__tests__/character-handlers.test.ts는 2 tests 통과했다.
    • Route/API: /ai-characters/:characterId/audio-contents/ai-characters/:characterId/audio-contents/:contentIdProtectedAdminShell에 연결했다. 목록 API는 characterId, page, size, 2자 이상 search_word만 보내며 status, isActive, active query를 보내지 않는다. 상세 API는 timezone=Asia/Seoul을 필수 query로 보낸다.
    • Boundary parse: 목록은 OpenAPI data.totalCount/items, 상세는 AudioContentDetailResponse shape를 Zod schema로 파싱한다.
    • Player safety: 목록과 상세는 Phase 1 AdminAudioPlayerAudioPlaybackProvider만 조합한다. 명시적 download button을 추가하지 않았고 media error는 일반 오류·수동 재시도·페이지 새로고침 안내만 표시한다. focused test는 media error 후 detail/list GET 재호출과 play 자동 재호출이 없음을 확인했다.
    • Signed URL: AdminAudioPlayer 기존 seam의 console/log/storage test와 P4-T2 detail integration test로 signed URL log·storage 호출이 없음을 확인했다. 분석 event seam은 현재 코드base에 없어서 코드 검사로 확인했다.
    • Mock mode: mock handler는 실제 endpoint shape로 list/detail을 제공하고 data:audio/wav preview asset을 사용한다. 브라우저에서 mock 로그인 후 /ai-characters/101/audio-contents?search=달빛&page=0&size=20 목록, 상세 이동, timezone=Asia%2FSeoul, player 재생 버튼, keyboard Space, volume, speed control을 확인했다.
    • Verification: 관련 테스트 npm run test:run -- src/app/App.test.tsx src/layouts/CharacterWorkspaceLayout.test.tsx src/shared/ui/__tests__/admin-audio-player.test.tsx src/shared/mocks/__tests__/character-handlers.test.ts src/features/audio-contents/tests/audio-list.test.tsx src/features/audio-contents/tests/audio-player.test.tsx는 6 files / 43 tests 통과했다. npm run typechecknpm run lint는 출력 없이 exit 0이었다.
    • LSP: 변경 TS/TSX 파일 diagnostics는 모두 clean이었다. src/shared/mocks/audio-content-fixtures.ts는 수정 직후 fresh diagnostics가 1회 timeout됐고, 이후 typecheck로 보완했다.
    • Visual QA: Playwright mock browser에서 1280px와 375px 상세 screenshots p4-t2-audio-detail-1280.png, p4-t2-audio-detail-375.png를 캡처했다. 이 환경의 subagent tool은 oracle을 지원하지 않아 /visual-qa dual-oracle pass는 실행하지 못했다.
  • 남은 항목: Audio 전용 오류 message key, backend container/codec 검증 계약, price 최대값, active-only 반환 보장은 P4-T1과 동일하게 backend 제공 대기다. P4-T3 form/upload와 P4-T4 E2E는 구현하지 않았다.

P4-T3 Audio 발행 form·upload 구현, 2026-07-28

  • 무엇을: Audio theme 선택, 즉시/예약 발행, create/update/deactivate multipart API, cover/audio validation, upload progress/cancel/retry adapter, form UI, route, mock store/handler를 구현하고 P4-T3 체크박스 18개를 완료로 표시했다.
  • 왜: AUDIO-009, AUDIO-011, AUDIO-014, AUDIO-023~024, AUDIO-027, AUDIO-033과 OpenAPI AudioContentCreateMultipart, AudioContentUpdateMultipart, AudioContentTheme 계약을 client form과 mock mode에서 고정하기 위해서다.
  • 어떻게:
    • RED: npm run test:run -- src/features/audio-contents/tests/audio-contract.test.ts src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-form.test.tsx는 최초 실행에서 audio-cover-policy, AudioContentFormPage, upload-audio-content import 부재로 3 files failed / 0 tests를 확인했다.
    • GREEN: 같은 focused command는 3 files / 11 tests 통과했다. 생성 multipart는 contentFile, coverImage, request 필수와 성공 data.contentId, 수정은 request와 optional coverImage, deactivate는 {isActive:false}만 확인했다.
    • Form/validation: 생성 form은 theme 미선택 submit을 차단하고 price를 “캔” 형식으로 표시하며 즉시 공개 기본값은 releaseDate=null, timezone="Asia/Seoul"을 보낸다. 예약 공개는 미래 local datetime만 허용하고 yyyy-MM-dd HH:mm로 보낸다. 수정 form은 theme/release/series/content file 변경 control을 만들지 않고 읽기 전용 안내만 표시한다.
    • File/upload: cover는 Phase 1 FileField·ImageCropDialogAUDIO_COVER_POLICY {aspect:1,maxWidth:800,noUpscale:true,maxBytes:10485760}을 조합한다. audio는 공통 policy로 MP3/AAC/M4A, .m4a + audio/x-m4a, 1,024,000,000 bytes 허용과 WAV·1,024,000,001 bytes 거부를 확인하며 codec sniffing과 resumable upload는 만들지 않았다. 415는 ApiError message/errorProperty를 form 안내로 보존한다.
    • Mock mode: GET /api/v2/admin/ai-characters/audio-content-themes, POST /audio-contents, PUT /audio-contents/:contentId handler와 같은 mock store의 create/update/deactivate list/detail 갱신을 추가했다. Audio 전용 오류 key, price 상한, active-only server 보장, backend container/codec 판정은 추정하지 않았다.
    • OQ-009: 실제 Audio 생성/수정 form에서 title, detail, tags 입력 공간과 반복 항목을 확인했다. 권고안은 title 80자, detail 2,000자, tags comma-separated 20개/각 30자이나 backend 호환 전까지 client 상한은 구현하지 않았다.
    • Verification: focused command는 3 files / 11 tests 통과했고, 관련 회귀 npm run test:run -- src/app/App.test.tsx src/layouts/CharacterWorkspaceLayout.test.tsx src/shared/mocks/__tests__/character-handlers.test.ts src/shared/validation/file-media-policy.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__/confirm-deactivate-dialog.test.tsx src/shared/ui/__tests__/unsaved-changes-guard.test.tsx src/features/audio-contents/tests/audio-list.test.tsx src/features/audio-contents/tests/audio-player.test.tsx는 11 files / 56 tests 통과했다. npm run typechecknpm run lint는 출력 없이 exit 0이었다.
    • Manual QA: Playwright mock browser에서 login → /ai-characters/101/audio-contents/new → theme 선택 → audio/cover upload → crop 적용 → create → detail → edit → update → deactivate → list toast를 실제 조작했다. artifacts는 .playwright-mcp/p4-t3-audio-created-detail.png, .playwright-mcp/p4-t3-audio-updated-detail.png, .playwright-mcp/p4-t3-audio-deactivated-list.png, .playwright-mcp/p4-t3-console.txt, .playwright-mcp/p4-t3-network.txt이다. console warning/error 0건, Audio 관련 network 15건은 모두 200이었다.
    • LSP/Diff: 변경 TS/TSX 파일 diagnostics는 clean이었다. src/shared/mocks/character-mock-store.ts diagnostics는 fresh 응답이 1회 timeout됐고 이후 typecheck로 보완했다. git diff --check -- src/app/browser-location.ts src/app/protected-admin-shell.tsx src/app/route-paths.ts src/features/audio-contents/api/audio-content-api.ts src/features/audio-contents/pages/AudioContentDetailPage.tsx src/features/audio-contents/pages/AudioContentListPage.tsx src/shared/mocks/audio-content-fixtures.ts src/shared/mocks/character-mock-store.ts src/shared/mocks/handlers.ts docs/20260725_AI캐릭터관리자웹/plan-task.md는 출력 없이 exit 0이었다.
  • 남은 항목: Audio 전용 backend 오류 message key, 실제 container/codec 검증, price 상한, active-only server 결과는 기존 P4-T1/P4-T2와 동일하게 backend 계약 제공 대기다. P4-T4 반응형·접근성 E2E와 Gate는 구현하거나 완료 표시하지 않았다.

P4-T3 blocker 수정 및 fresh QA 재검증, 2026-07-28

  • 무엇을: Audio 생성 기본 경로가 XHR upload adapter를 우회하지 않도록 고정하고, upload cancel/error/retry, malformed success JSON, .m4a MIME 경계, crop 취소, 415 field mapping, 비활성화 destructive 스타일, mock request logging을 재검증했다.
  • 왜: P4-T3 완료 기록 이후 발견된 blocker가 create/upload 실제 경로와 민감정보 비기록 제약을 깨뜨릴 수 있어, P4-T4/GATE로 확장하지 않고 P4-T3 범위 안에서만 닫기 위해서다.
  • 어떻게:
    • RED/GREEN: npm run test:run -- src/shared/mocks/browser.test.tsquiet: true 누락으로 1 failed를 확인했고, startMockWorkerquiet: true를 추가한 뒤 npm run test:run -- src/shared/mocks/browser.test.ts src/shared/mocks/__tests__/auth-handlers.test.ts src/shared/mocks/browser.test.ts는 2 files / 11 tests 통과했다.
    • Focused P4-T3: npm run test:run -- src/features/audio-contents/tests/audio-contract.test.ts src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-upload.test.tsx는 4 files / 16 tests 통과했다. 확장 focused npm run test:run -- src/features/audio-contents/tests/audio-contract.test.ts src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-upload.test.tsx src/shared/validation/file-media-policy.test.ts src/shared/ui/__tests__/confirm-deactivate-dialog.test.tsx src/shared/mocks/browser.test.ts src/shared/mocks/__tests__/auth-handlers.test.ts는 8 files / 36 tests 통과했다.
    • Static/build: npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다.
    • LSP: src/shared/mocks/browser.ts, src/shared/mocks/browser.test.ts fresh diagnostics는 clean이었다. 이전 변경 파일 중 일부 LSP 호출은 timeout됐지만 typechecklint로 보완했다.
    • Manual QA: npm run dev:mock 서버에서 Playwright Chromium으로 login → /ai-characters/101/audio-contents/new → 예약 공개 선택 → audio/cover 선택 → crop 적용 → create → detail → edit → update → deactivate confirm → list 이동 → 375px mobile list를 실제 조작했다. fresh artifacts는 .playwright-mcp/p4-t3-final-quiet/01-create-form.png부터 08-mobile-list.png, console.txt, network.txt, summary.json이다.
    • Visual evidence: PNG signature 8건을 확인했고 look_at으로 create 초기 form, 예약/파일 선택 form, destructive deactivate dialog, mobile list/player 상태를 확인했다. .playwright-mcp/p4-t3-final-quiet/summary.jsonscreenshotCount=8, containsPassword=false, containsMultipartBoundary=false, containsUploadFileName=false였다. console.txt는 Vite/React dev 안내 3건뿐이고 password·multipart body·upload filename은 없었다. Audio/Character API request 48건은 모두 200이었다.
    • Diff 검사: git diff --check -- src/shared/mocks/browser.ts src/shared/mocks/browser.test.ts docs/20260725_AI캐릭터관리자웹/plan-task.md는 출력 없이 exit 0이었다.
  • 남은 항목: Audio 전용 backend 오류 message key, 실제 container/codec 검증, price 상한, active-only server 결과는 기존 P4-T1/P4-T2와 동일하게 backend 계약 제공 대기다. P4-T4 반응형·접근성 E2E와 Phase 4 Gate는 구현하거나 완료 표시하지 않았다.

P4-T3 review gap closure, 2026-07-28

  • 무엇을: src/app/App.test.tsx를 protected shell/error/logout 보존 테스트로 분리해 모든 변경 test file을 250 pure LOC 이하로 낮추고, Audio/Character 비활성화 dialog와 Audio form의 사용자 표시 copy에서 내부 ID, 영문 개발 용어, hard delete, native file input 문구 노출을 제거했다.
  • 왜: P4-T3 review에서 oversized test, 비활성화 제목 줄바꿈, 안정 artifact 명명, upload progress 증거, Mock Preview overlap, 내부/영문 operator copy가 남아 있어 P4-T4/Gate로 확장하지 않고 P4-T3 안에서 닫기 위해서다.
  • 어떻게:
    • RED/GREEN: copy 회귀 테스트는 OQ-009, Audio 수정 계약, hard delete, native file chooser 문구가 남아 실패했고, Korean admin copy와 파일 선택 button wrapper로 수정한 뒤 npm run test:run -- src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-upload.test.tsx src/shared/ui/__tests__/confirm-deactivate-dialog.test.tsx src/features/characters/tests/CharacterDetailPage.test.tsx가 4 files / 13 tests 통과했다. App split 보존은 npm run test:run -- src/app/App.test.tsx src/app/App.protected-shell.test.tsx src/app/App.protected-errors.test.tsx src/app/App.logout.test.tsx가 4 files / 29 tests 통과했다.
    • Focused/static: npm run test:run -- src/features/audio-contents/tests/audio-contract.test.ts src/features/audio-contents/tests/audio-upload.test.ts src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-upload.test.tsx src/app/App.test.tsx src/app/App.protected-shell.test.tsx src/app/App.protected-errors.test.tsx src/app/App.logout.test.tsx src/shared/ui/__tests__/confirm-deactivate-dialog.test.tsx src/features/characters/tests/CharacterDetailPage.test.tsx src/shared/mocks/browser.test.ts src/shared/validation/file-media-policy.test.ts는 12 files / 58 tests 통과했다. npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다.
    • Size/LSP/diff: 변경 source/test 12개는 모두 250 pure LOC 이하였고, 변경 TS/TSX 파일 LSP diagnostics는 clean이었다. git diff --check는 P4-T3 변경 파일과 이 문서에 대해 출력 없이 통과했다.
    • Manual QA: npm run dev:mock 서버에서 Playwright Chromium으로 login → Audio create form → 예약 공개 → audio/cover 선택 → crop 적용 → upload progress in-flight 상태 → create detail → edit/save → deactivate confirm → list 이동 → 375px mobile list를 실제 조작했다. Fresh artifacts는 .playwright-mcp/p4-t3-final-quiet/01-create-form.png부터 10-mobile-list.png, console.txt, network.txt, summary.json이다.
    • Visual evidence: PNG signature 10건은 모두 89504e470d0a1a0a였고, 04-upload-progress.png업로드 중, 업로드 취소, progressbar를 표시한다. 08-deactivate-confirm.png오디오 콘텐츠 비활성화 확인완전 삭제는 아니며 목록에서 제외됩니다.를 표시하고 내부/영문 용어가 없다. 10-mobile-list.png는 overlap 없이 표시됐다. summary.jsonscreenshotCount=10, containsPassword=false, containsMultipartBoundary=false, containsUploadFileName=false, visibleInternalTerms=false였다.
    • 정정: 최종 gap closure 후 08-deactivate-confirm.png의 비활성화 문구는 목록 노출만 중지하며 콘텐츠는 보관됩니다.로 갱신됐고, 02-cover-crop-dialog.png는 640×480 PNG fixture와 예상 결과 480 × 480px crop preview를 표시하도록 재캡처했다. summary.jsonscreenshotCount=10, extraPngs=[], pngSignaturesOk=true, containsPasswordText=false, containsMultipartBoundary=false, containsUploadFileName=false, containsInternalVisibleTerms=false로 갱신됐다.
  • 남은 항목: Audio 전용 backend 오류 message key, 실제 container/codec 검증, price 상한, active-only server 결과는 기존 P4-T1/P4-T2와 동일하게 backend 계약 제공 대기다. P4-T4 반응형·접근성 E2E와 Phase 4 Gate는 구현하거나 완료 표시하지 않았다.

P4-T4 Audio 반응형·접근성 구현, 2026-07-28

  • 무엇을: Audio mock E2E에 mobile route capability, desktop/tablet mutation action, 320px overflow, keyboard-only player/form, 200% zoom, axe critical·serious 0건 검증을 추가하고 P4-T4 체크박스 4개를 완료로 표시했다. P4-GATE는 완료 표시하지 않았다.
  • 왜: PRD 9의 mobile 조회·재생 전용 범위와 desktop/tablet 운영 범위를 Audio route에서 분리하고, player/form 접근성 회귀를 Phase 4 Gate 전에 고정하기 위해서다.
  • 어떻게:
    • RED: npm run e2e:mock -- tests/e2e/audio-content.spec.ts는 최초 실행에서 20 tests 중 4 failed / 13 passed / 3 skipped였고, 320px mobile list의 오디오 생성 button이 visible이라 route capability 기대를 만족하지 못했다.
    • GREEN: AudioContentFormPage에서 (min-width: 768px) route capability를 적용해 mobile 직접 /new·/edit route는 생성/수정/업로드/비활성화 form을 mount하지 않고 안내만 표시한다. 목록 오디오 생성과 상세 수정 entrypoint는 md 이상에서만 노출한다. AudioContentSummary, mobile list item, AdminAudioPlayer error 영역은 긴 텍스트 wrapping과 min-w-0을 보강했다.
    • Focused unit/component: npm run test:run -- src/features/audio-contents/tests/audio-list.test.tsx src/features/audio-contents/tests/audio-player.test.tsx src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-upload.test.tsx는 4 files / 11 tests 통과했다.
    • Mock E2E: npm run e2e:mock -- tests/e2e/audio-content.spec.ts는 최종 실행에서 20 tests 중 17 passed / 3 skipped였다. Skipped 3건은 기존 browser 조건에 맞춘 keyboard path skip으로 mobile mutation 차단 및 WebKit tab focus 차이를 명시했다.
    • Static/build: npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. npm run build는 Vite production build에서 204 modules transformed, dist/assets/index-B5HKASem.js 381.66 kB(gzip 106.65 kB)를 생성했다.
    • LSP/size/diff: 변경 TS/TSX와 config 파일의 개별 LSP diagnostics는 clean이었고 AudioContentFormPage.tsx 개별 fresh diagnostics는 2회 timeout됐으나, src/features/audio-contents directory diagnostics는 14 TSX files scanned / errors 0 / diagnostics 0이었다. 변경 source/test/config 파일 pure LOC는 playwright.config.ts 29, tests/e2e/audio-content.spec.ts 137, AudioContentFormPage.tsx 104, AudioContentListPage.tsx 98, AudioContentDetailPage.tsx 63, AudioContentSummary.tsx 22, AudioContentListItem.tsx 32, admin-audio-player.tsx 125로 모두 250 이하이다. git diff --check와 untracked E2E spec 대상 git diff --check --no-index /dev/null tests/e2e/audio-content.spec.ts는 출력 없이 통과했다.
  • 남은 항목: Phase 4 Gate의 server mode 통합 검증과 Phase 5+ 범위는 수행하지 않았다. 현재 작업트리에는 P4-T4 외 선행 작업의 수정·untracked 파일이 함께 남아 있어 이 기록은 P4-T4 변경과 검증 결과만 대상으로 한다.

P4-T4 독립 검증, 2026-07-28

  • 무엇을: P4-T4 구현본을 main agent가 fresh 명령과 fresh browser evidence로 재검증하고, 독립 Oracle 2건으로 기능·시각/CJK 검증을 완료했다.
  • 왜: worker의 완료 보고만으로 P4-T4를 수용하지 않고, 현재 작업트리 기준 route capability, 320px overflow, keyboard-only, 200% zoom, axe, P4-GATE 미완료 상태를 별도 증거로 확인하기 위해서다.
  • 어떻게:
    • E2E: npm run e2e:mock -- tests/e2e/audio-content.spec.ts는 20 tests 중 17 passed / 3 skipped, exit 0이었다. skipped 3건은 mobile mutation route 차단과 WebKit tab focus 차이로 제한한 keyboard-only path다.
    • Focused unit/component: npm run test:run -- src/features/audio-contents/tests/audio-list.test.tsx src/features/audio-contents/tests/audio-player.test.tsx src/features/audio-contents/tests/audio-form.test.tsx src/features/audio-contents/tests/audio-form-upload.test.tsx는 4 files / 11 tests 통과했다.
    • Static/build: npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. build는 204 modules transformed와 dist/assets/index-B5HKASem.js 381.66 kB(gzip 106.65 kB)를 기록했다.
    • LSP/diff/size: src/features/audio-contents, src/shared/ui/admin-audio-player.tsx, tests/e2e/audio-content.spec.ts diagnostics는 clean이었다. P4-T4 대상 git diff --check는 출력 없이 통과했다. 순수 LOC는 playwright.config.ts 29, tests/e2e/audio-content.spec.ts 137, AudioContentFormPage.tsx 104, AudioContentListPage.tsx 98, AudioContentDetailPage.tsx 63, AudioContentSummary.tsx 22, AudioContentListItem.tsx 32, admin-audio-player.tsx 125로 모두 250 이하이다.
    • Browser evidence: .playwright-mcp/p4-t4-audio-responsive/01-mobile-list-320.png부터 06-desktop-detail-1280.png까지 6개 fresh PNG를 캡처했고, PNG signature는 모두 89504e470d0a1a0a였다. 320px 목록·상세·mobile edit guidance, 768px form, 1280px 상세의 scrollWidth === clientWidth로 horizontal overflow 0건을 확인했다. console warning/error는 favicon 404 외 0건이었다.
    • 독립 리뷰: 기능 Oracle은 PASS/HIGH로 mobile /new·/edit가 data fetch/form mount 없이 guidance만 반환하고, list/detail/player availability, desktop/tablet action, keyboard-only, axe/zoom coverage, P4-GATE 미완료 상태를 확인했다. Visual/CJK Oracle은 PASS/HIGH로 6개 screenshot에서 overflow, control clipping, awkward Korean break, mutation control leakage가 없다고 판정했다.
  • 남은 항목: P4-GATE server mode 통합 검증은 아직 수행하지 않았고 완료 표시하지 않았다. Audio 전용 backend 오류 message key, 실제 container/codec 검증, price 상한, active-only server 결과는 기존 backend 계약 대기 항목으로 유지한다.

P4-GATE 부분 검증 및 미완료 기록, 2026-07-28

  • 무엇을: Phase 4 Gate 명령을 fresh로 실행해 mock UI journey, server-mode 상태, static/build 결과를 분리 기록했다. P4-GATE는 완료로 판정하지 않았다.
  • 왜: mock mode 통과는 최종 UI 확인 증거이지만 실제 server integration 완료 증거가 아니며, README의 server mode testMatch 교집합 규칙 때문에 filtered Audio spec 실행 결과를 별도 해석해야 하기 때문이다.
  • 어떻게:
    • Preflight: git status --short로 현재 작업트리에 P4 선행 변경과 untracked QA/source 파일이 함께 있음을 확인했고, unrelated 변경은 보존했다. plan-task.md의 P4-GATE 명령과 README npm run e2e/npm run e2e:mocktestMatch 제한 문구를 재확인했다.
    • Mock unit/component: npm run test:run -- src/features/audio-contents src/shared/ui src/shared/validation은 21 files / 55 tests 통과했다.
    • Mock E2E: npm run e2e:mock -- tests/e2e/audio-content.spec.ts는 20 tests 중 17 passed / 3 skipped, exit 0이었다. skipped 3건은 mobile mutation route 차단과 WebKit tab focus 차이를 명시한 keyboard-only path다.
    • Server filtered command: npm run e2e -- tests/e2e/audio-content.spec.tsError: No tests found.로 종료했다. 현재 playwright.config.ts의 server mode testMatchserver-mode-boundary, smoke, auth, accessibility-shell만 포함하므로 audio-content.spec.ts와 교집합이 없다. 따라서 이 결과는 Audio server integration pass가 아니라 미검증 상태다.
    • Server allowlist sanity: bare npm run e2e는 현재 server allowlist 36 tests를 모두 통과했다. 이 명령은 server mode가 browser MSW로 fallback하지 않는 shell/boundary 검증이며, Audio search_word, timezone, contentFile, releaseDate, theme field, 수정 금지 control의 실제 server integration 완료 증거는 아니다.
    • Static/build: npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. build는 204 modules transformed와 dist/assets/index-B5HKASem.js 381.66 kB(gzip 106.65 kB)를 기록했다.
  • 남은 항목: P4-GATE 완료에는 실제 server mode에서 Audio flow가 실행되어 search_word, timezone, contentFile, releaseDate, theme field와 수정 금지 control이 network 요청·화면에 일치한다는 fresh evidence가 필요하다. Audio 전용 backend 오류 message key, 실제 container/codec 검증, price 상한, active-only server 결과는 backend 계약 제공 대기 상태로 유지한다.

P5-T1 Series 계약 확인 및 실행 범위 확정, 2026-07-28

  • 무엇을: OpenAPI Series 9개 operation과 Series* schema를 PRD SERIES-001~018, MOCK-001~009, 외부 의존 EXT-002, EXT-007, EXT-011과 대조해 P5-T1 체크박스 6개를 완료로 표시했다. PRD는 이미 같은 요구사항과 외부 의존을 반영하고 있어 수정하지 않았다.
  • 왜: P5-T2부터 계약에 없는 genre lookup, active-only server 결과, 도메인별 오류 key, missing ID와 concurrent conflict key를 추정하지 않고, 제공된 목록·상세·연결·순서 범위만 구현하게 하기 위해서다.
  • 어떻게:
    • JSON parse: node -e "JSON.parse(require('fs').readFileSync('docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json','utf8')); console.log('JSON.parse ok')"JSON.parse ok를 출력하고 exit 0으로 OpenAPI JSON 파싱을 확인했다.
    • Series 계약 확인: focused node 검사는 exit 0으로 Series operation 9개, SeriesCreateRequest, SeriesUpdateRequest, SeriesListResponse, SeriesListItem, SeriesDetailResponse, SeriesContentListResponse, SeriesContentSearchItem, SeriesContentAddRequest, SeriesOrderUpdateRequest의 required/properties를 출력했다. genre path count: 0으로 genre lookup endpoint 부재도 확인했다.
    • 요청·응답 고정: 연결 후보는 GET .../contents/search?search_word=..., 연결은 {contentIdList}, 해제는 body 없는 DELETE, 순서는 {ids}다. 목록과 연결 콘텐츠 목록은 page, size, data.totalCount, data.items[]를 사용한다.
    • 외부 의존 고정: 장르 lookup, active-only 반환 보장, Series 도메인별 오류 key, 순서 저장 missing ID와 concurrent conflict key는 미제공이므로 UI와 mock에서 추정하지 않는다. Series 수정 초기화는 상세 응답의 수정용 원본값으로 처리한다.
    • 상태·component map: 목록·상세·form·연결·순서 화면의 loading, empty, error, read-only, 저장 중, 충돌 또는 재조회 안내 상태와 action inventory를 기록했다. Page는 route/query/policy와 data loading만 조합하고, Feature component가 Series 규칙을 소유하며, 공통 상태·pagination·dialog·file/crop·guard는 Phase 1 shared contract를 사용한다.
    • Mock scenario: 제공 계약 범위의 목록·상세·연결 콘텐츠 목록·후보 검색·연결·해제·전체 순서 happy path와 공통 오류만 browser fixture 대상으로 확정했다. genre lookup, active-only, 충돌 오류는 fixture에서 제외한다.
    • 문서 추적: rg -n 'SERIES-01[4-8]|EXT-002|EXT-007|EXT-011|contents/search|contentIdList|\{ids\}|genre' docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md는 exit 0으로 PRD와 plan의 Series 계약, 외부 의존, 연결·순서 키워드 추적을 확인했다.
    • Diff 검사: git diff --check -- docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md는 출력 없이 exit 0으로 문서 공백 오류 0건을 확인했다.
  • 남은 항목: P4-GATE는 이전 기록처럼 미완료다. P5-T1에서는 애플리케이션 코드, tests, mocks, config, DESIGN.md, QA artifacts를 변경하지 않았고 P5-T2 이상과 P5-GATE는 완료 표시하지 않았다.

P5-T2 Series 목록·상세 조회 구현, 2026-07-28

  • 무엇을: 선택한 Character workspace 아래 Series read-only 목록/상세 vertical slice를 구현하고 P5-T2 체크박스 4개만 완료로 표시했다.
  • 왜: OpenAPI가 제공한 SeriesListResponseSeriesDetailResponse의 서로 다른 DTO를 그대로 표시하고, 계약 없는 genre lookup·active-only filter·mutation action을 추정하지 않기 위해서다.
  • 어떻게:
    • RED 1: npm run test:run -- src/features/series/tests/series-contract.test.ts는 테스트 파일 부재 상태에서 No test files found, exiting with code 1로 실패했다.
    • RED 2: 같은 명령은 테스트 추가 후 Failed to resolve import "@/features/series/api/series-api"로 실패해 production Series slice 부재를 확인했다. 중간에 .ts 파일 JSX parse 오류 1회는 createElement(App)으로 test harness만 수정해 올바른 RED로 재실행했다.
    • GREEN: npm run test:run -- src/features/series/tests/series-contract.test.ts는 1 file / 4 tests 통과했다.
    • Related route/mock: npm run test:run -- src/app/App.test.tsx src/app/App.protected-shell.test.tsx src/shared/mocks/__tests__/character-handlers.test.ts는 3 files / 17 tests 통과했다.
    • REFACTOR/regression: npm run test:run -- src/features/series/tests/series-contract.test.ts src/features/audio-contents/tests/audio-list.test.tsx src/features/audio-contents/tests/audio-player.test.tsx src/features/characters/tests/character-list.test.tsx는 4 files / 8 tests 통과했다. 최종 회귀 npm run test:run은 52 files / 200 tests 통과했다.
    • Static/build: npm run typecheck, npm run lint, git diff --check -- src/features/series src/shared/mocks src/app src/layouts docs/20260725_AI캐릭터관리자웹/plan-task.md는 출력 없이 exit 0이었다. npm run build는 production build를 완료했다.
    • LSP/LOC: 변경 TS/TSX 파일 diagnostics는 clean이었다. src/shared/mocks/series-handlers.ts 단일 fresh diagnostics는 반복 timeout됐지만 src/shared/mocks directory diagnostics는 16 files / 0 diagnostics였고 typecheck로 보완했다. handlers.ts는 Series handler 분리 후 pure LOC 236으로 250 이하가 됐다.
    • Manual QA: npm run dev:mock 서버에서 Playwright Chromium으로 login → /ai-characters/101/series?page=0&size=20 → 목록 확인 → detail link → /ai-characters/101/series/5001 → reload를 실제 조작했다. Artifacts는 .playwright-mcp/p5-t2-series-readonly/list-1280.png, detail-1280.png, detail-reload.png, 각 snapshot, list-action-check.json, detail-dom-check.json, detail-reload-check.json, network/console 파일이다. list-action-check.jsonsearchboxCount: 0, forbiddenActions: [], visibleSeriesText: true였다. detail-dom-check.jsonhasDetailStrings: true, hasReverseEnumLeak: false, forbiddenActions: []였다. detail-reload-check.jsontitleVisible: true, stringsVisible: true였다. Series network는 목록 ?page=0&size=20와 상세 /series/5001만 200으로 기록됐다. Console은 기능과 무관한 /favicon.ico 404 1건만 있었다.
    • Visual evidence: look_at으로 3개 screenshot을 확인했고, 목록은 시리즈 2건과 상세 보기·pagination만 보이며 상세/reload는 표시 문자열만 보이고 생성/수정/비활성화/연결/순서 변경 control 및 명백한 clipping/layout 깨짐이 없었다. 이 harness의 subagent tool은 oracle을 지원하지 않아 /visual-qa dual-oracle pass는 실행하지 못했다.
    • Final QA refresh: refactor 후 npm run dev:mock 서버에서 Playwright Chromium으로 login → /ai-characters/101/series?page=0&size=20 → 상세 링크 → /ai-characters/101/series/5001 → reload를 다시 조작했다. Fresh artifacts는 .playwright-mcp/p5-t2-series-final/list-1280.png, detail-1280.png, detail-reload.png, list-action-check.json, detail-dom-check.json, detail-reload-check.json, list-network.txt, detail-network.txt, console-warnings.txt다. 목록은 visibleSeriesText: true, searchboxCount: 0, forbiddenControls: []였고 상세는 hasDetailStrings: true, hasReverseEnumLeak: false, forbiddenControls: []였다. reload는 titleVisible: true, stringsVisible: true, URL /ai-characters/101/series/5001을 유지했다. Series network는 목록과 상세만 200이었고 console은 기능과 무관한 /favicon.ico 404만 있었다.
  • 남은 항목: Series 생성·수정·비활성화, genre lookup, active-only server 보장, 연결·해제·순서 UI, P5-T3~P5-GATE는 구현하거나 완료 표시하지 않았다. P4-GATE도 이전 기록처럼 미완료다.

P5-T4 Series Audio 연결·해제·전체 순서 구현, 2026-07-28

  • 무엇을: Series 상세에 연결된 Audio 목록/후보 검색/연결/해제 UI를 추가하고, 전체 Series 순서 관리 route와 keyboard 위/아래 저장 흐름을 구현해 P5-T4 체크박스만 완료로 표시했다.
  • 왜: 제공된 page/size + totalCount/items, search_word, { contentIdList }, body 없는 DELETE, { ids } 계약을 추정 없이 고정하고 P5-T3의 genre lookup과 form 범위, P5-T5/GATE 범위는 건드리지 않기 위해서다.
  • 어떻게:
    • RED contract: npm run test:run -- src/features/series/tests/series-contract.test.ts는 1 file / 6 tests 중 2 failed, TypeError: getSeriesContents is not a function, TypeError: getAllSeriesForOrder is not a function으로 실패했다.
    • RED linked UI: npm run test:run -- src/features/series/tests/series-contents.test.tsxFailed to resolve import "@/features/series/components/SeriesContents"로 실패했다.
    • RED order UI: npm run test:run -- src/features/series/tests/series-order.test.tsxFailed to resolve import "@/features/series/pages/SeriesOrderPage"로 실패했다.
    • RED E2E: playwright.config.ts가 새 spec을 제외해 최초 No tests found가 났고, mock testMatchseries.spec.ts를 추가한 뒤 npm run e2e:mock -- tests/e2e/series.spec.ts는 4 failed, 연결된 오디오 heading not found로 실패했다.
    • GREEN contract: npm run test:run -- src/features/series/tests/series-contract.test.ts는 1 file / 6 tests 통과했다. 새 contract는 현재 연결 목록 GET이 ?page=1&size=20만 보내고, 후보 검색이 contents/search?search_word=...만 보내며, POST body가 JSON.stringify({ contentIdList: [9002, 9003] }), DELETE body가 undefined, PUT body가 JSON.stringify({ ids: [5002, 5001] })임을 고정한다.
    • GREEN linked UI: npm run test:run -- src/features/series/tests/series-contents.test.tsx는 1 file / 3 tests 통과했다. linked list pagination, 중복 후보 제거, 단일 선택 POST, title/영향 confirmation, body-less DELETE, mutation 실패 시 현재 목록 보존과 재조회/재시도 안내를 확인했다.
    • GREEN order UI: npm run test:run -- src/features/series/tests/series-order.test.tsx는 1 file / 2 tests 통과했다. totalCount 기반 page 0/1 전체 fetch, 위/아래 button reorder, { ids } 저장, 실패 시 현재 UI 순서 보존과 재조회/재시도 안내를 확인했다.
    • E2E: npm run e2e:mock -- tests/e2e/series.spec.ts는 Chromium/WebKit/Mobile Chrome/Mobile Safari 4 tests 통과했다. login → /ai-characters/101/series/5001 → 후보 검색/연결 → 해제 confirmation/DELETE → order page → 위로 button → save → SPA refetch 순서 유지 흐름을 확인했다.
    • Final focused regression: npm run test:run -- src/features/series는 3 files / 12 tests 통과했다.
    • Static: npm run typechecknpm run lint는 출력 없이 exit 0이었다.
    • Manual QA: npm run dev:mock 서버에서 Playwright MCP로 login → /ai-characters/101/series/5001 → linked list pagination 확인 → 후보 아침 검색 → 중복 후보 제거와 선택/연결 → title/영향 confirmation 후 해제 → /series/order 위/아래 button 저장 → detail로 돌아갔다가 order 재진입해 저장 순서 유지까지 실제 조작했다. Artifacts는 .playwright-mcp/p5-t4-series-link-order/01-detail-linked-list.png, 02-network-linked-list.txt, 03-after-link.png, 04-network-link-post.txt, 05-unlink-confirmation.png, 06-after-unlink.png, 07-network-unlink-delete.txt, 08-order-page-before.png, 09-order-after-save.png, 10-network-order-save.txt, 11-order-refetch-persisted.png, 12-console-warning-error.txt다. Network evidence는 linked GET이 /contents?page=0&size=20만, search가 /contents/search?search_word=%EC%95%84%EC%B9%A8만, DELETE가 /contents/9001, order page load가 /series?page=0&size=50임을 기록했다. Console은 기능과 무관한 /favicon.ico 404 1건만 있었다. 정정: 독립 검증에서 OpenAPI reorder path가 plural PUT /api/v2/admin/ai-characters/{characterId}/series/orders임을 확인해 follow-up에서 API/mock endpoint와 contract evidence를 /series/orders로 수정했다. Fresh browser 재확인에서는 /ai-characters/101/series/order에서 keyboard 위로 이동 후 순서 저장PUT https://test-character-admin.sodalive.net/api/v2/admin/ai-characters/101/series/orders와 body {"ids":[5002,5001]}가 발생했고 singular /series/order PUT은 0건이었다. Fresh screenshot은 .playwright-mcp/p5-t4-series-link-order/13-order-plural-endpoint-fresh.png다.
    • Follow-up DTO/focus 정정: 독립 검증에서 Series content list/search DTO가 invented detail·coverImageUrl을 쓰고 unlink alertdialog가 기존 useModalFocus pattern을 쓰지 않는 blocker를 확인했다. OpenAPI 기준 list item은 contentId, coverImage, title, isAdult, search item은 contentId, title, coverImage만 parse/display하도록 수정하고, unlink dialog는 ConfirmDeactivateDialog와 같은 modal focus entry/trap pattern을 적용했다. 추가 품질 리뷰에서 Escape 닫힘과 Shift+Tab/focus return 회귀 검증 부족이 남아 Escape close와 trigger focus return test를 보강했고, focused/series/mock E2E/typecheck/lint/build 및 재리뷰 PASS를 확인했다.
    • OQ-009: 실제 연결 UI는 checkbox 후보를 한 번에 확인·해제해야 하므로 contentIdList 권고안은 50개/요청, 실제 순서 UI는 keyboard 위/아래 조작과 전체 저장의 인지 부하를 고려해 ids 권고안은 200개/캐릭터로 기록한다. backend 호환 확인 전까지 client 상한은 구현하지 않았다.
  • 남은 항목: P5-T3는 genre lookup과 상세 수정용 원본값 제공 전까지 pending/blocked이고, P5-T5와 P5-GATE는 완료 표시하지 않았다. P4-GATE도 이전 기록처럼 미완료다. 실제 backend의 domain-specific conflict/missing-id response key는 추정하지 않고 generic 재조회/재시도 안내만 구현했다.

P5-T5 Series 반응형·접근성 capability 완료, 2026-07-28

  • 무엇을: 활성 Series 범위(P5-T2 read-only 목록/상세, P5-T4 연결 콘텐츠·전체 순서)만 대상으로 320px 모바일 조회 capability, desktop/tablet 관리 capability, keyboard-only 연결·해제·정렬, 200% zoom, axe critical/serious 0건을 tests/e2e/series.spec.ts로 고정하고 P5-T5 활성 체크박스를 완료로 표시했다.
  • 왜: P5-T3의 genre lookup과 상세 수정용 원본값 제공이 아직 필요해 요일·genre/form 접근성은 구현·테스트하지 않고 대기 항목으로 분리해야 하며, 모바일에서는 제공되지 않은 CRUD와 P5-T4 mutation action이 route capability로 차단돼야 하기 때문이다.
  • 어떻게:
    • RED: npm run e2e:mock -- tests/e2e/series.spec.ts는 새 P5-T5 coverage 추가 후 24 tests 중 4 failed / 3 skipped / 17 passed로 실패했다. 실패 원인은 320px Series 상세에서 검색어 searchbox가 visible이라 모바일 연결 mutation action이 차단되지 않은 것이었다. locator strictness 보정 전 1차 실패는 테스트 결함으로 수정 후 RED를 재확인했다.
    • GREEN implementation: Series 전용 useSeriesManagementCapability를 추가해 (min-width: 768px) 이상에서만 연결 검색/연결/해제와 전체 순서 route를 활성화했다. 320px 상세는 연결 콘텐츠 목록만 조회하고, /series/order 직접 진입은 태블릿 이상 안내만 표시한다. P5-T3 create/edit/delete/genre UI/API는 추가하지 않았다.
    • Focused E2E: 최종 npm run e2e:mock -- tests/e2e/series.spec.ts는 24 tests 중 21 passed / 3 skipped, exit 0이었다. skipped 3건은 mobile mutation route 차단과 WebKit tab focus 차이를 명시한 keyboard-only path다. E2E는 320px list/detail/order guidance overflow 0건, desktop/tablet 768·1280 linked search/link/unlink/order save, keyboard-only search/select/link, unlink dialog focus/Escape/focus return, order up/save, 200% zoom, axe critical/serious 0건을 확인한다.
    • Focused component: npm run test:run -- src/features/series/tests/series-contents.test.tsx는 1 file / 3 tests 통과했다. 기존 component tests는 desktop management capability를 명시하도록 갱신했다.
    • Static/build: 최초 npm run typechecknpm run buildSeriesContents test fixture의 canManageSeries prop 누락으로 실패했고, fixture 수정 후 npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다. build는 215 modules transformed와 dist/assets/index-Bw6rMAMS.js 405.92 kB(gzip 110.63 kB)를 기록했다.
    • LSP/LOC: src/features/series directory diagnostics는 10 files / 0 diagnostics, tests/e2e directory diagnostics는 9 files / 0 diagnostics였다. 단일 tests/e2e/series.spec.ts fresh diagnostics는 반복 3초 timeout됐지만 directory diagnostics, typecheck, lint로 보완했다. 순수 LOC는 use-series-management-capability.ts 19, SeriesDetailPage.tsx 62, SeriesContents.tsx 161, SeriesOrderPage.tsx 102, series-contents.test.tsx 103, series.spec.ts 167로 모두 250 이하이다.
    • Visual QA: 1차 pass A는 PASS였고 pass B는 320px 공통 header의 모바일 메뉴 열기, AI 캐릭터, 로그아웃 CJK 음절 줄바꿈을 blocker로 반환했다. protected-admin-shell.tsx header controls에 shrink-0 whitespace-nowrap을 적용하고 App.protected-shell.test.tsx 회귀 test를 추가했다. RED npm run test:run -- src/app/App.protected-shell.test.tsxwhitespace-nowrap class 부재로 1 failed였고, 수정 후 7 tests passed였다. 최종 7개 fresh screenshot .playwright-mcp/p5-t5-series-responsive/11-final-mobile-320-list.png부터 17-final-desktop-1280-order.png까지 PNG signature 89504e470d0a1a0a, 모든 route hasHorizontalOverflow:false, header computed whiteSpace:"nowrap"였고, visual QA pass B 재검토는 PASS였다.
  • 남은 항목: P5-T3는 genre lookup과 상세 수정용 원본값 제공 전까지 pending/blocked이며 요일·genre/form 접근성 체크도 대기한다. P5-GATE는 완료 표시하지 않았다. P4-GATE도 이전 기록처럼 미완료다. 실제 server mode Series integration과 active-only/domain-specific 오류 key는 기존 외부 의존 상태를 유지한다.

P5-GATE Series 부분 검증, 2026-07-28

  • 무엇을: Phase 5 Gate 명령을 현재 제공된 Series 계약 범위에서 fresh 실행하고, mock UI journey와 server mode 실행 가능 상태를 분리해 기록했다.
  • 왜: P5-T3 genre lookup과 상세 수정용 원본값 제공이 아직 필요하고, server mode Series spec은 현재 Playwright server allowlist와 file filter 교집합에서 실행되지 않으므로 Gate 완료를 주장하면 안 되기 때문이다.
  • 어떻게:
    • npm run test:run -- src/features/series는 3 files / 12 tests 통과했다.
    • npm run e2e:mock -- tests/e2e/series.spec.ts는 24 tests 중 21 passed / 3 skipped로 통과했다. skipped 3건은 mobile mutation 차단과 WebKit tab-focus 차이로 명시됐다.
    • npm run e2e -- tests/e2e/series.spec.tsNo tests found로 실행되지 않았다. 이는 server mode testMatch와 직접 file filter의 교집합 문제이며, Series 실제 server integration 성공 증거가 아니다.
    • npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. build는 215 modules transformed와 dist/assets/index-vqn3JSuw.js 406.01 kB(gzip 110.65 kB)를 기록했다.
  • 남은 항목: P5-GATE 완료에는 Series server mode spec이 실제로 실행되어 목록·상세·연결·해제·순서 network가 개발 API 계약과 일치한다는 fresh evidence가 필요하다. P5-T3 genre lookup, 상세 수정용 원본값, active-only 보장, Series 도메인별 오류 key도 외부 의존으로 유지한다.

P6-T1 Community 계약 확인, 2026-07-28

  • 무엇을: OpenAPI Community 3개 operation과 CommunityPost* schema를 PRD COMMUNITY-001~015, 최신 외부 의존 EXT-007~008, Phase 6 계획과 대조해 P6-T1 체크박스 6개를 완료로 표시했다.
  • 왜: P6-T2 이후 목록/Sheet/form 구현이 detail GET, pagination metadata, 수정 audio/price, 오류 key, price 상한을 추정하지 않게 하기 위해서다.
  • 어떻게:
    • OpenAPI parse: node -e ...로 Community path가 GET/POST /api/v2/admin/ai-characters/{characterId}/community-posts, PUT /api/v2/admin/ai-characters/{characterId}/community-posts/{postId} 3개 operation임을 확인했다.
    • 요청·응답 고정: 목록은 필수 timezone, page, size query와 배열 data를 사용한다. 생성 multipart는 optional audioFile, optional postImage, required request이고 request는 required content, isCommentAvailable, isAdult, optional price다. 수정 multipart는 optional postImage, required request만 허용하고 request field는 content, isCommentAvailable, isAdult, isActive, isFixed다. POST/PUT 성공은 NullSuccessdata=null만 신뢰한다.
    • 제외 고정: Community 목록 응답에는 totalCount, page, hasNext, isActive, fixedAtUtc가 없으므로 fixture·DTO·UI에 추가하지 않는다. 수정 request에는 priceaudioFile이 없으므로 수정 audio/price UI나 request를 만들지 않는다. Community detail GET과 detail/edit route도 만들지 않는다.
    • 오류/외부 의존: 공통 오류 fixture는 ApiErrorResponsesuccess=false, message, data=null, errorProperty와 OpenAPI에 있는 400/401/403/404/405/406/415/500 status만 사용한다. Community 전용 message key와 media upload 오류 key는 추정 분기하지 않고, price 최대값은 99999, 파일 용량·MIME backend 동일 검증은 확인됐다. active-only 반환과 pagination 종료 metadata는 EXT-007~008 제공 전까지 추정하지 않는다.
    • 상태·component map: list Page는 characterId, timezone=Asia/Seoul, page, size, items, loading, empty, error, retry, selected sheet item을 소유한다. Sheet는 목록 item source로 열리며 별도 detail fetch 없이 update/pin/soft delete action과 cache invalidation을 소유한다. Form/media component는 Community content/isAdult/comment/price/postImage/audioFile 규칙을 소유하고, shared FileField/ImageCropDialog/AdminAudioPlayer/validation은 Phase 1·4 contract를 재사용한다.
    • Mock scenario: deterministic seed는 고정 게시글, 이미지 게시글, audioUrl 게시글, 댓글 요약이 있는 게시글을 포함한다. mock handler는 list timezone/page/size, create multipart, update multipart, pin/unpin, soft delete isActive=false·isFixed=false, data=null, detail GET 0회를 검증한다. active-only server 결과, pagination 종료, Community 전용 오류 key, price 상한은 mock에서도 추정하지 않는다.
    • 문서 추적: rg -n 'COMMUNITY-01[2-5]|EXT-00[7-9]|EXT-010|EXT-011|CommunityPost|postImage|audioFile|timezone|totalCount|hasNext|fixedAtUtc' docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json는 exit 0으로 PRD·plan·OpenAPI의 Community 계약과 외부 의존 범위를 확인했다.
  • 남은 항목: P6-T2부터 실제 Community list/Sheet route, API, mock, tests를 TDD로 구현한다. Comments는 Phase 8 범위이며 P6에서 구현하지 않는다.

P6-T2 Community 목록·collection Sheet 구현, 2026-07-28

  • 무엇을: Community posts 목록 route와 목록 item 기반 Sheet 수정·고정/해제·비활성화 흐름을 구현하고 P6-T2 체크박스 8개를 완료로 표시했다.
  • 왜: 제공 계약에 없는 Community detail GET/route, 검색, totalCount, hasNext, fixedAtUtc, active filter를 만들지 않고 목록 응답만으로 collection Sheet mutation을 끝내기 위해서다.
  • 어떻게:
    • RED: focused test 최초 실행은 import/route 부재와 UI가 Character list로 fallback되는 실패를 확인했고, 이후 @/features/community-posts/model/types 부재와 Sheet mutation 미발생 실패를 거쳐 P6-T2 경로를 고정했다.
    • GREEN focused: npm run test:run -- src/features/community-posts/tests/community-contract.test.ts src/features/community-posts/tests/community-list.test.tsx src/features/community-posts/tests/community-sheet.test.tsx는 3 files / 8 tests 통과했다.
    • Static/build: npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. src/features/community-posts, src/app, src/shared/mocks directory LSP diagnostics는 0 diagnostics였고, Community 관련 source/test/mock 파일은 모두 pure LOC 250 이하이다. git diff --check -- src/features/community-posts src/app/route-paths.ts src/app/browser-location.ts src/app/protected-admin-shell.tsx src/layouts/CharacterWorkspaceLayout.tsx src/shared/mocks/community-post-fixtures.ts src/shared/mocks/community-post-handlers.ts src/shared/mocks/community-post-mock-store.ts src/shared/mocks/handlers.ts src/shared/mocks/character-mock-store.ts docs/20260725_AI캐릭터관리자웹/plan-task.md는 출력 없이 exit 0이었다.
    • Review closure: 목록 price는 0 이상 정수로 파싱하고 update schema는 isActive=false만 허용하도록 고정했다. Sheet mutation 실패 시 role="alert" 오류를 표시하고 저장 상태를 해제하는 회귀 test를 추가했다. Sheet test의 multipart payload 검증 중복은 contract test로 이동해, Sheet test는 버튼 조작→PUT 호출→목록 갱신만 확인한다.
    • Contract: 목록 request는 timezone=Asia%2FSeoul&page=size 순서의 timezone/page/size만 보내고 배열 data만 소비한다. update/soft delete는 multipart request part와 성공 data=null을 처리하며 soft delete request는 { isActive: false, isFixed: false }이고 isActive=true를 보내지 않는다.
    • Router/UI: /ai-characters/:characterId/community-posts를 workspace route로 추가했고 /community-posts/:postId/edit은 앱 route가 아니며 detail GET handler 호출 0건을 test로 고정했다.
    • Mock: community-post-handlers와 list store가 timezone/page/size, update multipart, pin/unpin, soft delete를 list store만으로 처리한다. Community detail handler는 만들지 않았다.
    • Manual QA: npm run dev:mock 서버에서 Playwright MCP로 login → /ai-characters/101/community-posts?page=0&size=20 → 목록 item Sheet 열기 → 내용 수정/댓글 허용 해제/성인 콘텐츠 체크 → 수정 저장고정 해제고정하기비활성화를 실제 조작했다. Network evidence는 list GET이 ?timezone=Asia%2FSeoul&page=0&size=20만 사용하고 각 PUT 뒤 list GET이 재실행됨을 보였다. Soft delete 뒤 성공 알림 커뮤니티 게시글을 비활성화했습니다.와 남은 목록 2건을 확인했다. Artifacts는 p6-t2-community/03-after-edit.png, 04-after-unpin.png, 05-after-pin.png, 06-after-delete.png, network-after-unpin.txt, network-after-pin.txt, network-after-delete.txt, console-final.txt다. Console은 기능과 무관한 /favicon.ico 404만 있었다. look_at으로 06-after-delete.png를 확인해 성공 알림, 목록 표시, 명백한 한글 clipping/overflow 없음도 확인했다.
  • 남은 항목: Community 생성, postImage/audioFile form, GIF/media policy, 반응형·접근성 E2E, 실제 server integration, active-only 반환과 pagination 종료 metadata는 P6-T3~P6-GATE와 외부 의존 범위로 남겼다.

P6-T3 Community 게시글 form·첨부 media 구현, 2026-07-28

  • 무엇을: Community 게시글 생성 route/form, create multipart serializer, update Sheet의 postImage-only 첨부, JPEG/PNG crop·GIF 원본 보존·audio policy·player/no-refetch 규칙을 구현하고 P6-T3 체크박스 12개를 완료로 표시했다.
  • 왜: OpenAPI가 제공한 생성·수정 multipart 계약만 구현하고, 범위 밖인 댓글, detail GET, edit route, update audio/price, GIF 재인코딩, URL refresh-only request, mobile mutation capability를 추정하지 않기 위해서다.
  • 어떻게:
    • RED: npm run test:run -- src/features/community-posts/tests/community-contract.test.ts src/features/community-posts/tests/community-form.test.tsx src/features/community-posts/tests/community-list.test.tsx src/features/community-posts/tests/community-sheet.test.tsx 최초 실행은 CommunityPostFormPage import 부재, createCommunityPost is not a function, mock POST timeout으로 실패해 생성 API/page/mock 부재를 확인했다.
    • GREEN focused: 최종 npm run test:run -- src/features/community-posts/tests/community-contract.test.ts src/features/community-posts/tests/community-form.test.tsx src/features/community-posts/tests/community-list.test.tsx src/features/community-posts/tests/community-sheet.test.tsx는 4 files / 14 tests 통과했다. 이후 fresh focused npm run test:run -- src/features/community-posts src/shared/validation도 실행해 Community와 shared file policy 회귀를 확인했다.
    • Contract/form: create는 POST /community-posts에 optional audioFile, optional postImage, required request를 보내고 request는 content, isCommentAvailable, isAdult, optional price만 허용한다. create schema는 strict라 isActiveisFixed를 거부한다. update는 PUT /community-posts/:postId에 optional postImage와 request content, isCommentAvailable, isAdult, isActive=false, isFixed만 허용하며 audioFileprice를 보내지 않는다.
    • Media: JPEG/PNG는 기존 ImageCropDialog와 crop helper를 사용해 aspect: "free", maxWidth: 800, noUpscale: true로 전송한다. GIF는 Community 전용 정책에서만 허용하고 crop/canvas/re-encode 없이 원본 File을 전송하며 width 800px은 허용, 801px은 제출 전 오류로 차단한다. 첨부 audio는 기존 AUDIO_FILE_POLICYFileField를 사용해 MP3/AAC/M4A, audio/x-m4a, 1,024,000,000 bytes, WAV 거부를 유지한다.
    • Player/cache: audioUrl이 있으면 desktop row, mobile card, Sheet에 AdminAudioPlayer를 렌더링한다. audio error와 manual retry는 detail/list refetch, URL 재발급, autoplay를 일으키지 않는 test로 고정했다. 새 audioUrl은 create/update 후 정상 list refetch로만 화면에 반영된다.
    • Mock: community-post-handlers가 POST/PUT multipart contract를 검증하고 list store에 local preview image/audio data URL을 반영한다. Community detail handler는 만들지 않았다.
    • Static/build: 변경 TS/TSX 파일과 src/features/community-posts, src/shared/mocks directory LSP diagnostics는 0 diagnostics였다. npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다.
    • Manual QA: npm run dev:mock 서버에서 Playwright MCP로 login → /ai-characters/101/community-posts?page=0&size=20커뮤니티 게시글 생성 link → 빈 submit validation 내용을 입력하세요. → content/price 입력 → 생성/ai-characters/101/community-posts 복귀 → 성공 알림과 새 행 브라우저 QA 생성 게시글 표시를 실제 조작했다. Screenshot artifact는 p6-t3-community-create-list.png다. Console은 기능과 무관한 /favicon.ico 404만 있었다.
    • Visual QA: read-only visual QA pass A/B는 모두 PASS였다. Pass A는 생성 route 이동, form validation, 성공 후 목록 복귀, 새 행/성공 배너, DESIGN.md token/component 재사용에 blocker가 없다고 판정했다. Pass B는 p6-t3-community-create-list.png에서 한국어 CJK clipping, 잘린 버튼/라벨, 깨진 레이아웃이 없고 기존 카드/테이블 패턴과 일치한다고 판정했다.
    • OQ-009: 실제 Community Sheet/form의 content textarea와 목록 줄바꿈을 확인했다. 권고안은 content 2,000자이며, backend 호환 확인 전까지 client 상한은 구현하지 않았다.
  • 남은 항목: Community 반응형·접근성 E2E와 mobile mutation capability는 P6-T4, 실제 server integration과 active-only 반환·pagination 종료 metadata는 P6-GATE/외부 의존으로 남긴다. Comments, Community detail GET, /community-posts/:postId, /edit, search, totalCount/hasNext, fixedAtUtc, update audio/price, price max enforcement, GIF re-encoding, URL refresh-only request는 구현하지 않았다.

P6-T3 review 보완 검증 기록 (2026-07-28):

  • 무엇을: 비활성 캐릭터의 Community 생성/Sheet mutation 진입 차단, mock Community characterId 격리, .m4aaudio/mp4/audio/x-m4a 허용, 유효한 mp4 data URL fixture, mock multipart part/cardinality 검증, update postImage preview 반영, async image 준비 중 submit disable, Sheet multipart 검증 test를 보완했다.
  • 왜: P6-T3 review에서 활성 캐릭터 전제 누락, mock 격리 부족, audio MIME 경계와 multipart 계약 검증이 느슨한 부분이 확인됐기 때문이다.
  • 어떻게: RED focused는 npm run test:run -- src/features/community-posts/tests/community-contract.test.ts src/features/community-posts/tests/community-form.test.tsx src/features/community-posts/tests/community-sheet.test.tsx src/shared/validation/file-media-policy.test.ts로 기대 실패를 확인했다. GREEN focused는 같은 명령이 4 files / 26 tests passed, fresh feature 회귀 npm run test:run -- src/features/community-posts src/shared/validation이 5 files / 28 tests passed였다.
  • Static/build: npm run typecheck, npm run lint, npm run build, git diff --check가 모두 exit 0이었다.
  • Manual QA: 기존 npm run dev:mock 서버(http://127.0.0.1:8889)에서 Playwright MCP로 login → 비활성 캐릭터 /ai-characters/202/community-posts 생성 link 0건과 read-only 배너 확인 → /ai-characters/202/community-posts/new 직접 접근 시 커뮤니티 게시글 생성 차단생성 button 0건 확인 → 활성 캐릭터 /ai-characters/101/community-posts 생성 form validation → 새 게시글 생성 → Sheet 수정 저장 → Sheet 비활성화를 실제 조작했다. 결과 steps는 login-ok, inactive-list-readonly-ok, inactive-form-blocked-ok, active-create-ok, active-sheet-edit-ok, active-sheet-delete-ok였고 missing 항목은 없었다. Network는 login POST, Community create POST, Sheet update/soft delete PUT과 character-scoped list GET만 200으로 기록됐으며 Community detail GET은 없었다. p6-t3-community-create-list.png를 갱신했고 look_at 확인 결과 성공 배너와 목록이 보이며 명백한 한국어 clipping/layout 깨짐은 없었다. Console은 기능과 무관한 React DevTools 안내와 /favicon.ico 404뿐이었다.
  • 범위 유지: Comments, Community detail GET/route, search, total/hasNext/fixedAtUtc, active filter, server-specific error key, P6-T4 mobile mutation capability는 구현하지 않았다.

P6-T3 2차 review 보완 정정 기록 (2026-07-28):

  • 정정: 위 P6-T3 최초 RED 기록의 CommunityPostFormPage import 부재와 createCommunityPost is not a function, mock POST timeout은 §2.1 line 72의 "import 오류, test 환경 오류, 임시 mock 누락 같은 우발적 실패는 Red 증거로 인정하지 않는다" 규칙상 유효한 RED 증거가 아니다. 유효 RED는 2차 보완에서 추가한 mock handlers reject non-multipart bodies and text upload fields, mock handlers update postImage preview and use a valid bundled audio preview, Community Sheet keeps save impossible while image preparation is pending의 기대 실패다.
  • 무엇을: Community mock parser를 request.formData() 우선 strict parser로 바꾸고 raw multipart fallback에도 같은 part/cardinality/file-field 검증을 적용했다. text/plain raw body, string postImage, string audioFile, update의 audioFile과 unknown/duplicate part를 400으로 거부한다. Community mock audio fixture는 기존 잘린 audio/mp4가 아니라 Audio mock의 decodable WAV data URL을 재사용한다. Sheet는 이미지 준비 중이거나 crop dialog가 열린 동안 수정 저장/고정/비활성화 mutation을 disabled 처리한다. community-sheet.test.tsx는 support를 분리해 250 pure LOC 아래로 낮췄다.
  • 왜: 두 번째 review에서 playable Community mock audio, strict multipart boundary parsing, Sheet pending image race, update image URL proof, test file size, 문서 overclaim이 남았기 때문이다.
  • RED: npm run test:run -- src/features/community-posts/tests/community-contract.test.ts src/features/community-posts/tests/community-sheet.test.tsx는 추가 직후 2 files 중 3 tests failed였다. 실패는 text/plain raw multipart가 200으로 통과, previewCommunityPostAudioUrldata:audio/mp4;base64,AAAAIGZ0eXBtcDQy라 WAV 기대 불일치, Sheet 이미지 준비 중 수정 저장 button이 disabled가 아닌 문제였다.
  • GREEN focused: npm run test:run -- src/features/community-posts/tests/community-contract.test.ts src/features/community-posts/tests/community-sheet.test.tsx는 2 files / 16 tests passed였다. npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium은 Chromium 1 test passed로 mock Community audio data:audio/wav metadata load, errorCode=null, duration > 0을 확인했다.
  • 파일 지도 정정: 실제 P6-T3 추가/수정 파일은 src/features/community-posts/validation/community-post-media-policy.ts, src/features/community-posts/components/CommunityPostForm.tsx, src/features/community-posts/tests/community-form.test.tsx, src/features/community-posts/tests/community-test-support.ts, tests/e2e/community.spec.ts, src/shared/mocks/{audio-content-fixtures.ts,community-post-fixtures.ts,community-post-mock-store.ts}, playwright.config.ts를 포함한다. community-post-schema.ts, community-media-policy.ts와 과거 잘못 기재된 Community E2E 파일명은 실제 파일명이 아니므로 위 파일 지도에서 정정했다.
  • 범위 유지: Comments, Community detail GET/route, search, total/hasNext/fixedAtUtc, active filter, server-specific error key, P6-T4 mobile mutation capability는 구현하지 않았다.

P6-T3 2차 review 최종 검증 기록 (2026-07-28):

  • 무엇을: 2차 보완 후 Community mock parser, playable audio fixture, Sheet pending state, test size, 문서 기록을 현재 작업트리 기준으로 재검증했다.
  • 왜: mock mode 통과와 문서 기록만으로 server integration 또는 P6-T4 완료를 과장하지 않고, P6-T3 blocker만 닫혔는지 확인하기 위해서다.
  • 자동 검증: npm run test:run -- src/features/community-posts src/shared/validation은 5 files / 31 tests passed였다. npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium은 1 passed로 Community mock audio metadata load를 확인했다. npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다. build는 226 modules transformed와 dist/assets/index-Dw4zhRsK.js 429.63 kB(gzip 115.07 kB)를 기록했다.
  • LSP/size: src/features/community-posts diagnostics는 9 TSX files / 0 diagnostics, src/shared/mocks diagnostics는 19 TS files / 0 diagnostics, tests/e2e diagnostics는 10 TS files / 0 diagnostics였다. 단일 playwright.config.ts, CommunityPostForm.tsx, community-post-mock-store.ts, community-form.test.tsx fresh diagnostics는 일부 3초 timeout됐지만 directory diagnostics, typecheck, lint로 보완했다. 순수 LOC는 community-contract.test.ts 165, community-form.test.tsx 162, community-list.test.tsx 92, community-sheet.test.tsx 155, community-test-support.ts 180, tests/e2e/community.spec.ts 34, community-post-mock-store.ts 221, community-post-fixtures.ts 75, audio-content-fixtures.ts 118, CommunityPostSheet.tsx 148, CommunityPostForm.tsx 131, playwright.config.ts 29로 모두 250 이하이다.
  • Browser QA: 기존 mock 서버 http://127.0.0.1:8889에서 Playwright Chromium으로 login → /ai-characters/101/community-posts 이동 → 커뮤니티 게시글 7003 오디오 플레이어 확인 → audio[src^='data:audio/wav'] metadata load를 실제 실행했다. 결과는 kind="loaded", errorCode=null, duration=1, readyState=4, srcPrefix="data:audio/wav;base64,"였다. Network는 Community list GET 2건만 200으로 기록됐고 Community detail GET은 없었다. Console warning/error는 기능과 무관한 /favicon.ico 404뿐이었다.
  • Visual evidence: fresh screenshot p6-t3-community-audio-list.png를 저장했고 PNG signature는 89504e470d0a1a0a, size는 207392 bytes였다. look_at 확인 결과 Community 목록과 오디오 플레이어가 보이고, 재생 시간 0:00 / 0:01, 재생/위치/볼륨/속도 controls가 표시되며, 명백한 한국어 clipping이나 깨진 레이아웃은 없었다.
  • 남은 항목: P6-T4 mobile/viewport/focus/axe capability와 P6-GATE server mode integration은 아직 완료 표시하지 않는다. active-only 반환과 pagination 종료 metadata는 기존 외부 의존으로 유지한다.

P6-T3 final review close 기록 (2026-07-28):

  • RED: npm run test:run -- src/features/community-posts/tests/community-form.test.tsx src/features/community-posts/tests/community-contract.test.ts src/features/community-posts/tests/community-sheet.test.tsx는 3 files 중 3 tests failed였다. 실패는 crop dialog open 상태에서 생성이 disabled가 아님, raw multipart declared/body boundary mismatch가 200으로 통과, Sheet player 접근성 쿼리가 전역 중복 name에 취약한 문제였다.
  • GREEN focused: 같은 명령은 3 files / 22 tests passed였다. CommunityPostFormcropSource !== null 동안 disabled와 submit guard를 유지하고 apply 시 image error를 정리한다. raw multipart fallback은 Content-Type의 declared boundary와 body delimiter line이 다르면 null 처리한다. Sheet test는 전역 <audio> count 대신 list 외부 player와 Sheet dialog player를 accessible name으로 확인한다.
  • 최종 검증: npm run test:run -- src/features/community-posts src/shared/validation는 5 files / 31 tests passed, npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium은 1 passed, npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이다. rg -n "community-post\.spec" docs/20260725_AI캐릭터관리자웹/plan-task.md는 출력이 없다.

P6-T3 final blocker 재검증 기록 (2026-07-28):

  • 무엇을: 최종 review에서 확인된 Community image MIME/확장자 불일치, 비동기 image 준비 stale 결과, raw multipart fallback 종료 경계, image 준비 중 선택 취소 blocker를 회귀 test와 최소 수정으로 닫았다.
  • 왜: .gif + image/png, .png + image/gif, .jpg + image/png 같은 불일치 조합이 Community GIF 무가공 경로를 깨뜨릴 수 있고, 느린 image 준비 Promise가 최신 선택/취소 상태를 덮거나 submit을 영구 disabled 상태로 남길 수 있으며, mock raw multipart parser가 잘못된 body를 수용하면 계약 test가 실제 multipart 경계를 충분히 보장하지 못하기 때문이다.
  • RED: npm run test:run -- src/features/community-posts/tests/community-form.test.tsx src/features/community-posts/tests/community-sheet.test.tsx src/features/community-posts/tests/community-contract.test.ts는 3 files 중 4 tests failed였다. 실패는 image MIME/extension mismatch 허용, stale crop dialog 표시, raw multipart trailing data 허용이었다. 추가 RED npm run test:run -- src/features/community-posts/tests/community-form.test.tsx는 1 failed로 image 준비 중 선택 취소생성 button이 disabled로 남는 문제를 확인했다.
  • GREEN: validateCommunityPostImageFile이 확장자별 MIME 조합을 확인하고, CommunityPostForm/CommunityPostSheet가 image selection token으로 stale Promise 결과를 무시하며, form의 file === null 경로가 pending 상태를 즉시 해제한다. parseRawMultipart는 body 시작과 closing boundary/trailing data를 검사한다.
  • 최종 자동 검증: npm run test:run -- src/features/community-posts/tests/community-form.test.tsx src/features/community-posts/tests/community-sheet.test.tsx src/features/community-posts/tests/community-contract.test.ts는 3 files / 27 tests passed, npm run test:run -- src/features/community-posts src/shared/validation은 5 files / 36 tests passed, npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium은 1 passed였다. npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다.
  • Diagnostics/size: src/features/community-postssrc/shared/mocks/community-post-mock-store.ts LSP diagnostics는 0건이다. 순수 LOC는 CommunityPostForm.tsx 143, CommunityPostSheet.tsx 159, community-post-media-policy.ts 55, community-post-mock-store.ts 224, community-form.test.tsx 217, community-sheet.test.tsx 176, community-contract.test.ts 181로 모두 250 이하이다.
  • 재리뷰: 최종 blocker 재리뷰는 PASS/HIGH로, MIME/확장자 불일치, stale async image selection, raw multipart 경계, form pending image 취소 blocker가 모두 root-cause 위치에서 닫혔고 신규 blocker가 없다고 판정했다. Context 재검토도 PASS/HIGH로 P6-T4와 server integration이 미완료 범위로 유지됨을 확인했다.

P6-T4 Community 반응형·접근성 capability 완료 기록 (2026-07-28):

  • 무엇을: Community에 Series와 같은 (min-width: 768px) 관리 capability를 적용해 모바일은 목록·Sheet·첨부 오디오 조회만 허용하고, 생성 route/form과 Sheet 수정·고정·비활성화 action은 차단했다. tablet/desktop은 기존 생성·Sheet 관리 흐름을 유지했다.
  • 왜: PRD 9P6-T4 범위가 모바일 등록·수정·고정·비활성화를 제외하고, mock UI 단계에서 focus trap/복귀·keyboard·320px·200% zoom·axe evidence를 Phase Gate 전에 독립적으로 고정해야 하기 때문이다.
  • RED: npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium은 7 tests 중 3 failed였다. 실패는 320px 생성 링크 노출, 320px 생성 route가 안내 대신 form을 표시, Sheet Escape가 닫히지 않는 문제였다.
  • GREEN: useCommunityManagementCapability(min-width: 768px) 기준으로 관리 가능 viewport를 계산하고, CommunityPostListPage는 생성 link와 Sheet canMutatecharacter.isActive && canManageCommunity로 제한한다. CommunityPostFormPage는 모바일에서 생성 안내 PageState만 렌더링한다. CommunityPostSheet는 Escape close와 기존 useModalFocus trap/복귀를 함께 검증한다.
  • Review blocker: code-quality review는 Sheet 내부 ImageCropDialog가 Escape를 부모 Sheet로 전파해 crop context를 잃을 수 있다고 REVISE/HIGH로 지적했다. 추가 RED npm run test:run -- src/shared/ui/__tests__/image-crop-dialog.test.tsx는 5 tests 중 1 failed로 onCancel이 0회 호출되는 문제를 확인했다. ImageCropDialog가 Escape에서 preventDefault, stopPropagation, onCancel을 실행하고 Tab은 기존 trap에 위임하도록 고쳤으며 같은 명령은 5 tests passed였다.
  • 최종 자동 검증: npm run test:run -- src/features/community-posts src/shared/ui/__tests__/image-crop-dialog.test.tsx는 5 files / 34 tests passed, npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium은 7 passed였다. npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다.
  • Visual QA blocker: 1차 visual QA는 320px 모바일 목록의 게시글 열기 버튼 CJK 줄바꿈과 fullPage screenshot evidence 크기 불일치를 REVISE/HIGH로 지적했다. 추가 RED npm run test:run -- src/features/community-posts/tests/community-list.test.tsx는 긴 visible button text로 1 failed였고, CommunityPostListItem/CommunityPostList의 visible label을 열기로 줄이되 full aria-label을 유지하며 open button과 desktop status cell에 whitespace-nowrap을 적용했다. 수정 후 같은 명령은 2 tests passed였다.
  • Diagnostics/review: LSP diagnostics는 src/features/community-posts/lib/use-community-management-capability.ts, CommunityPostListPage.tsx, CommunityPostFormPage.tsx, CommunityPostSheet.tsx, CommunityPostList.tsx, CommunityPostListItem.tsx, tests/e2e/community.spec.ts, src/shared/ui/image-crop-dialog.tsx, src/shared/ui/__tests__/image-crop-dialog.test.tsx 모두 0건이다. Spec review는 PASS/HIGH, blocker 재리뷰는 PASS/HIGH였고, visual QA 재리뷰 A/B도 viewport screenshot p6-t4-community-*-rerun.png 4개와 DOM evidence 기준 PASS/HIGH로 신규 blocker가 없다고 판정했다.
  • 남은 항목: P6-GATE server mode integration은 아직 완료 표시하지 않는다. active-only 반환과 pagination 종료 metadata는 기존 외부 의존으로 유지한다.

P6-GATE Community mock gate 부분 검증 기록 (2026-07-28):

  • mock/UI gate: npm run test:run -- src/features/community-posts src/shared/validation은 5 files / 36 tests passed였다. npm run e2e:mock -- tests/e2e/community.spec.ts는 24 passed / 4 skipped였다. Skipped 4건은 기존 정책과 동일하게 audio metadata decode를 Chromium 증거로 한정한 2건과 WebKit range input Tab focus 불안정으로 제외한 Sheet keyboard traversal 2건이다.
  • WebKit focus test 조정: 전체 browser project 실행에서 WebKit/mobile-safari가 range slider 재생 위치에 Tab focus를 안정적으로 주지 못해 Community sheet traps focus closes on Escape and returns focus가 실패했다. tests/e2e/audio-content.spec.ts의 WebKit keyboard path 제외 정책과 맞춰 Community Sheet keyboard traversal도 WebKit에서 skip하고, Chromium/mobile-chrome에서 focus trap·Escape close·trigger focus return을 유지 검증한다.
  • server integration: npm run e2e -- tests/e2e/community.spec.ts는 Playwright server-mode testMatch와 file filter 교집합이 없어 No tests found로 종료했다. 따라서 P6-GATE는 mock UI journey만 부분 통과로 기록하고, 실제 server integration 완료는 주장하지 않는다.
  • 최종 공통 검증: npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다. P6-GATE 완료 표시는 backend 외부 의존(EXT-007~EXT-008)과 server-mode 대상 spec 정합성이 해결된 뒤 재개한다.

P6-R2 Community 상태 boolean label 정리 완료 기록 (2026-07-30):

  • 무엇을: Community desktop row와 mobile card의 고정·댓글·성인 상태 표시를 Community 전용 helper로 통일하고, raw 성인 true/false 노출을 unit·mock E2E에서 막았다.
  • 왜: REV-P6-002가 지적한 성인 여부 raw boolean 표시를 PRD §13의 운영자용 한국어 label 기준으로 닫기 위해서다.
  • RED: npm run test:run -- src/features/community-posts는 변경 직후 일반 · 댓글 허용 · 일반 콘텐츠를 찾지 못하고 기존 DOM에 성인 false가 남아 실패했다.
  • GREEN/REFACTOR: formatCommunityPostStatus고정/일반 · 댓글 허용/댓글 차단 · 성인 콘텐츠/일반 콘텐츠를 반환하고 CommunityPostList/CommunityPostListItem이 이를 재사용한다. API DTO·multipart payload와 mock mutation shape는 변경하지 않았다.
  • 검증: npm run test:run -- src/features/community-posts는 5 files / 42 tests passed, npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium은 7 passed, npm run e2e:mock -- tests/e2e/community.spec.ts는 24 passed / 4 skipped였다.
  • 남은 항목: P6-R2 범위 없음. P6-GATE server mode integration은 기존 외부 의존·server-mode 대상 spec 정합성 항목으로 별도 유지한다.

P7-T1 FanTalk 계약 확인 완료 기록 (2026-07-28):

  • 무엇을: OpenAPI의 FanTalk 제공 범위를 목록 GET과 답변 POST로 확정하고, 별도 상세·답변 수정·답변 상태 filter·sort·유일성 오류 계약은 EXT-004 외부 의존으로 유지했다.
  • 계약 증거: OpenAPI path는 /api/v2/admin/ai-characters/{characterId}/fan-talks GET(listAiCharacterFanTalks)과 /api/v2/admin/ai-characters/{characterId}/fan-talks/{fanTalkId}/replies POST(createAiCharacterFanTalkReply) 2개뿐이다. schema는 FanTalkCreatorReply, FanTalkListItem, FanTalkListResponse, FanTalkReplyCreateRequest, FanTalkReplyResponse, FanTalkListApiResponse, FanTalkReplyApiResponse가 제공됐다.
  • 구현 기준: 목록은 page, size, fanTalkCount, fanTalks, hasNext, item creatorReplies[]를 소비한다. 답변 생성 request는 { content }만 보내고 성공 DTO의 fanTalkId, replyId, creatorMemberId, content, createdAtUtc를 반영한다.
  • 제외 기준: /fan-talks/:fanTalkId 직접 route, 상세 GET, 답변 수정·삭제, answer filter/sort query, 중복 생성 오류 status/message key 분기는 만들지 않는다. mock fixture도 목록·답변 생성 범위만 제공하고 미제공 계약을 추정하지 않는다.
  • 검증: OpenAPI JSON parse로 FanTalk path/schema를 출력했고, 예상 외 FanTalk path가 없음을 확인하는 Node 검사는 hasUnexpected:false로 exit 0이었다. rg -n 'FANTALK-0(0[7-9]|1[0-1])'는 PRD/plan의 외부 의존·확정 범위를 추적했다.

P7-T2 FanTalk 목록·답변 생성 완료 기록 (2026-07-28):

  • 무엇을: FanTalk 목록 route/page/component, list GET API, one reply POST Sheet/form, 제공 계약 범위의 mock store/handler와 mock E2E를 구현하고 P7-T2 체크박스 9개를 완료로 표시했다.
  • 왜: OpenAPI가 제공한 목록과 답변 1회 생성 범위만 UI로 연결하고, 상세·수정·삭제·filter/sort·중복 오류 key처럼 미제공 계약은 추정하지 않기 위해서다.
  • RED: npm run e2e:mock -- tests/e2e/fan-talk.spec.ts --project=chromium은 최초 No tests found였고, spec/config 추가 후 FanTalk 목록 조회 실패와 mock response shape 오류로 실패해 FanTalk mock GET/POST 부재를 확인했다.
  • GREEN: src/shared/mocks/fan-talk-{fixtures,mock-store,handlers}.ts를 추가하고 CharacterMockStore/handlers.ts에 list GET과 reply POST만 연결했다. E2E는 API request만 수집해 page/sizestatus/sort가 전송되지 않고, GET /fan-talks/7001 상세 요청이 0건이며, 미답변 → 답변 생성 → 읽기 전용 답변 표시가 유지됨을 확인한다.
  • Focused tests: npm run test:run -- src/features/fan-talks/tests/fan-talk-contract.test.ts src/features/fan-talks/tests/fan-talk-list.test.tsx src/features/fan-talks/tests/fan-talk-reply.test.tsx는 3 files / 8 tests passed였다.
  • Mock E2E: npm run e2e:mock -- tests/e2e/fan-talk.spec.ts는 Chromium, WebKit, Mobile Chrome, Mobile Safari 4 tests passed였다.
  • Static/build: npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다. build는 235 modules transformed와 dist/assets/index-BiTOuTXK.js 441.40 kB(gzip 116.90 kB)를 기록했다.
  • Review: 1차 독립 리뷰는 E2E가 query 없는 GET /fan-talks/7001 상세 요청을 놓칠 수 있다고 FAIL/HIGH로 지적했다. E2E request 수집을 { method, path, search }로 바꿔 exact detail GET 0건을 검증하도록 수정했고, 위 focused/mock/static 검증을 새로 실행했다.
  • OQ-009: 실제 FanTalk reply form은 짧은 단일 textarea와 저장 버튼만 제공한다. content 권고안은 2,000자이며 backend 호환 확인 전까지 client 상한은 구현하지 않았다.
  • 남은 항목: FanTalk 상세, 답변 수정·삭제, 전체 결과 filter/sort, 중복 생성 오류 status/message key는 EXT-004 외부 계약 제공 전까지 구현하지 않는다. P7-GATE server integration은 아직 완료 표시하지 않는다.

P7-T3 FanTalk 반응형·접근성 capability 완료 기록 (2026-07-28):

  • 무엇을: tests/e2e/fan-talk.spec.ts에 320/768/1280 viewport의 목록 조회·답변 작성, edit/delete action 부재, 320px keyboard viewport, keyboard-only Sheet create/focus return, 200% zoom, axe critical·serious 0건 검증을 추가하고 P7-T3 체크박스 3개를 완료로 표시했다.
  • 왜: P7-T2의 제공 계약 범위인 목록 GET과 답변 POST capability가 viewport별로 축소되지 않고, 미제공 상세·수정·삭제·filter/sort 기능을 UI나 network에 섞지 않기 위해서다.
  • 검증: npm run e2e:mock -- tests/e2e/fan-talk.spec.ts는 26 passed / 2 skipped였다. skipped 2건은 WebKit 계열의 button Tab focus 불안정으로 keyboard-only path를 Chromium/mobile-chrome 증거로 한정한 것이다. npm run typechecknpm run lint는 exit 0이었다. 단일 tests/e2e/fan-talk.spec.ts LSP diagnostics는 3초 timeout됐지만 typecheck/lint로 보완했다.
  • Review: 독립 리뷰는 차단 findings 없음으로, P7-T3 요구사항 충족과 P7-T2 범위 보존을 확인했다. 잔여 리스크는 WebKit keyboard-only skip, CSS zoom 기반 200% 근사, 실제 모바일 soft keyboard가 아닌 320x480 viewport 검증이다.
  • 남은 항목: P7-GATE server mode integration은 아직 완료 표시하지 않는다. FanTalk 상세, 답변 수정·삭제, 전체 결과 filter/sort, 중복 생성 오류 status/message key는 EXT-004 외부 계약 제공 전까지 구현하지 않는다.

P7-GATE FanTalk mock gate 부분 검증 기록 (2026-07-28):

  • mock/unit gate: npm run test:run -- src/features/fan-talks는 3 files / 8 tests passed였다. npm run e2e:mock -- tests/e2e/fan-talk.spec.ts는 26 passed / 2 skipped였다. Skipped 2건은 WebKit 계열 button Tab focus 불안정으로 keyboard-only path를 Chromium/mobile-chrome 증거로 한정한 것이다.
  • server integration: npm run e2e -- tests/e2e/fan-talk.spec.ts는 Playwright server-mode testMatch와 file filter 교집합이 없어 No tests found로 종료했다. 따라서 P7-GATE는 mock UI journey만 부분 통과로 기록하고, 실제 server integration 완료는 주장하지 않는다.
  • 최종 공통 검증: npm run typecheck, npm run lint, npm run build, git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md는 모두 exit 0이었다. P7-GATE 완료 표시는 server-mode 대상 spec 정합성과 실제 개발 API FanTalk 목록·답변 생성 검증이 해결된 뒤 재개한다.
  • 남은 항목: FanTalk 상세, 답변 수정·삭제, 전체 결과 filter/sort, 중복 생성 오류 status/message key는 EXT-004 외부 계약 제공 전까지 구현하지 않는다.

P7-R1 FanTalk 날짜·성공 문구 현지화 완료 기록 (2026-07-30):

  • 무엇을: FanTalk desktop list, mobile card, Sheet 원문, 등록된 답변 날짜 표시를 formatSeoulDateTime으로 통일하고, 답변 등록/수정 성공 status를 답변이 등록되었습니다., 답변이 수정되었습니다.로 바꿨다. API DTO의 ID와 UTC field는 contract/model mapping에 그대로 둔다.
  • RED: npm run test:run -- src/features/fan-talks는 raw UTC와 내부 ID success copy 기대 변경 후 3 files 중 2 failed / 3 failed tests로 실패했다. npm run e2e:mock -- tests/e2e/fan-talk.spec.ts --project=chromium은 목록 raw UTC 표시 assertion에서 1 failed / 6 passed로 실패했다.
  • GREEN/REFACTOR: FanTalkList.tsx, FanTalkListItem.tsx, FanTalkReplySheet.tsx가 기존 디자인 token/class 구조를 유지한 채 공통 formatter를 사용한다. 생성/수정 success status는 action-centered Korean copy만 표시하고, 반환 reply content/date는 Sheet 본문에만 표시한다.
  • Focused unit: npm run test:run -- src/features/fan-talks는 3 files / 13 tests passed였다.
  • Mock E2E: npm run e2e:mock -- tests/e2e/fan-talk.spec.ts --project=chromium은 7 passed였고, 전체 npm run e2e:mock -- tests/e2e/fan-talk.spec.ts는 26 passed / 2 skipped였다. skipped 2건은 기존 WebKit 계열 keyboard-only Tab focus 제외 정책이다.
  • Static/diagnostics: src/features/fan-talkstests/e2e LSP diagnostics는 오류 0건, npm run typecheck는 exit 0, targeted git diff --check는 no output이었다.
  • 수동 확인 대체: mock E2E가 desktop/mobile/card/Sheet/keyboard/200% zoom/axe 경로를 실제 브라우저로 실행해 raw UTC와 내부 ID 노출 0건, reply ID mapping 회귀 0건을 확인했다.

P8-T1 Comments 계약 부재 확인 및 Phase 제외 기록 (2026-07-28):

  • 무엇을: OpenAPI에서 댓글 CRUD path가 0건이고 summary schema AudioContentComment, CommunityPostComment만 제공됨을 확인해 P8-T1 체크박스 5개를 완료로 표시했다. P8-T2~P8-T4P8-GATE는 계약 제공 전까지 구현·완료 표시하지 않는다.
  • 계약 증거: OpenAPI의 comment 관련 path는 []였고, comment 관련 schema는 AudioContentComment, CommunityPostComment뿐이다. Audio 상세는 commentList/commentCount, Community 목록 item은 firstComment/commentCount 읽기용 요약만 포함한다.
  • 제외 기준: Audio·Community 댓글 목록, 작성, 수정, soft delete endpoint/query/request/response DTO가 없다. root/direct reply 2단계 server rule과 fan 댓글 삭제 권한 오류 status/message key도 없다. 따라서 endpoint 이름 추정, client-only permission 완료 주장, mock DTO·browser fixture 생성은 하지 않는다.
  • Screen inventory: Audio detail과 Community Sheet는 현재 summary 표시 또는 댓글 진입점 대기 상태만 가질 수 있다. 계약 제공 후 host Page/Sheet는 target 식별자와 query/cache 보존만 소유하고, Comments component가 2단계 thread·작성자별 action·권한 오류를 소유하며, form/dialog/live region은 Phase 1 shared contract를 재사용한다.
  • 검증: OpenAPI JSON parse와 comment path/schema 출력은 exit 0으로 commentPaths: []를 확인했다. rg -n "COMMENT-00[1-6]|댓글 CRUD|EXT-005|commentList|firstComment" docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json는 PRD/plan/OpenAPI의 외부 의존과 summary field 추적을 확인했다.
  • 남은 항목: EXT-005가 Audio·Community 댓글 CRUD와 fan 댓글 삭제 권한 오류 계약을 제공하면 P8-T2부터 재개한다. 계약 전에는 src/features/comments, tests/e2e/comments.spec.ts, Comments mock handler/fixture를 만들지 않는다.

P8-GATE Comments 제외/대기 기록 (2026-07-28):

  • 판정: 핵심 댓글 CRUD 계약이 미제공이므로 Phase 8은 mock UI journey와 server integration을 실행하지 않고 제외/대기 상태로 둔다.
  • 이유: Gate expected가 요구하는 Audio·Community 두 진입점의 2단계 댓글 CRUD·권한·모바일 최종 UI는 backend list/create/update/soft delete 계약과 권한 오류 계약이 있어야 검증할 수 있다. 현재 OpenAPI에는 summary DTO만 있어 mock fixture를 만들면 추정 구현이 된다.
  • 남은 항목: EXT-005 제공 후 npm run test:run -- src/features/comments, npm run e2e:mock -- tests/e2e/comments.spec.ts, npm run e2e -- tests/e2e/comments.spec.ts, npm run typecheck, npm run lint, npm run build를 fresh 실행해 P8-GATE를 재판정한다.

P9-T1 교차 journey·오류 회귀 완료 기록 (2026-07-28):

  • 무엇을: 활성 제공 범위만 대상으로 tests/e2e/resource-workflows.spec.tstests/e2e/error-mapping.spec.ts를 추가해 교차 mock journey, mock 오류 message/security, 금지 detail/filter/sort request, 비활성 캐릭터 하위 mutation 차단, server no-fallback 회귀를 고정했다. Comments CRUD는 EXT-005 미제공으로 제외 상태를 유지했다.
  • 왜: Phase 9는 미제공 계약을 포함한 전체 완료가 아니라, Phase 0~8에서 제공되거나 명시 제외된 활성 범위의 교차 회귀 증거만 남겨야 하기 때문이다.
  • RED: 최초 npm run e2e:mock -- tests/e2e/resource-workflows.spec.ts tests/e2e/error-mapping.spec.tsNo tests found로 시작했고, spec/config 추가 후 16 tests 중 resource journey, inactive FanTalk reply action, mock API error probe가 실패했다. 원인은 새 P9 spec 부재, 비활성 FanTalk 답변 affordance 노출, WebKit 계열 service worker controller 타이밍, 커뮤니티 저장 성공 UI 과잉 기대, 모바일에서 desktop-only Series mutation journey 실행이었다.
  • GREEN: mock testMatch에 P9 spec을 추가하고, FanTalk 목록/card가 character.isActive=false일 때 답변하기 button 대신 읽기 전용만 노출하도록 수정했다. error-mapping.spec.ts는 mock worker active와 controller를 기다린 뒤 공통 오류 fixture를 직접 probe한다. resource-workflows.spec.ts는 desktop-only mutation journey를 desktop project로 제한하고, mobile projects는 inactive mutation 차단 회귀만 유지한다.
  • Focused 검증: npm run test:run -- src/features/fan-talks는 3 files / 9 tests passed였다. npm run e2e:mock -- tests/e2e/resource-workflows.spec.ts tests/e2e/error-mapping.spec.ts는 14 passed / 2 skipped였다. skipped 2건은 desktop-only mutation journey를 mobile-chrome/mobile-safari에서 제외한 것이다.
  • 회귀/계약 검증: npm run test:run -- src/features는 26 files / 112 tests passed로 도메인 serializer, soft delete, media error, mock handler fixture 회귀를 재검증했다. npm run e2e -- tests/e2e/server-mode-boundary.spec.ts는 16 passed로 server mode 404/network error가 browser MSW/mock으로 fallback되지 않음을 확인했다.
  • Static/build: npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다. build는 235 modules transformed, dist/assets/index-CQH-_zZD.js 441.58 kB(gzip 117.05 kB)를 기록했다.
  • Diagnostics: FanTalkListItem.tsx, FanTalkListPage.tsx, fan-talk-list.test.tsx는 LSP diagnostics 0건이었다. FanTalkList.tsx, resource-workflows.spec.ts, error-mapping.spec.ts 단일 fresh diagnostics는 반복 3초 timeout됐지만 npm run typechecknpm run lint 통과로 보완했다.
  • 남은 항목: P9-T3 문서·품질 Gate는 아직 시작하지 않았다. P4/P5/P6/P7 server integration Gate 미완료와 P8 Comments 제외/대기는 기존 외부 의존·server-mode testMatch 상태로 유지한다.

P9-T2 반응형·접근성·보안 회귀 완료 기록 (2026-07-28):

  • 무엇을: tests/e2e/responsive-capabilities.spec.tstests/e2e/accessibility.spec.ts를 추가해 mock shell viewport/capability, 핵심 route axe, skip link/main focus, dialog focus return, reduced motion, bright theme, theme toggle 부재, 민감정보 비저장 smoke를 검증했다.
  • 왜: Phase 9는 새 기능 추가가 아니라 활성 제공 범위가 PRD 9, 접근성, 보안 저장소 기준을 계속 만족한다는 최신 교차 증거를 남겨야 하기 때문이다.
  • RED: 최초 npm run e2e:mock -- tests/e2e/responsive-capabilities.spec.ts tests/e2e/accessibility.spec.tsNo tests found로 시작했다. spec/config 추가 뒤에는 skip link focus, main focus, mobile theme-toggle absence assertion이 순차 실패해 테스트가 실제 회귀를 잡는 것을 확인했다.
  • GREEN: mock testMatch에 P9-T2 spec을 추가하고, ProtectedAdminShell의 skip link가 main에 실제 focus를 넘기도록 mainReftabIndex={-1}를 추가했다. keyboard traversal smoke는 desktop Chromium 증거로 한정하고, 없는 theme toggle은 toHaveCount(0)로 검증했다.
  • Focused 검증: npm run e2e:mock -- tests/e2e/responsive-capabilities.spec.ts tests/e2e/accessibility.spec.ts는 17 passed / 3 skipped였다. skipped 3건은 keyboard focus traversal을 mobile Chrome, WebKit, mobile Safari에서 제외한 desktop Chromium 전용 증거다.
  • 회귀/품질 검증: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts는 1 file / 3 tests passed였다. npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다. build는 235 modules transformed, dist/assets/index-BFpSc9vx.js 441.68 kB(gzip 117.07 kB)를 기록했다.
  • Diagnostics: responsive-capabilities.spec.ts, playwright.config.ts, src/shared/mocks/__tests__/mode-boundary.test.ts는 LSP diagnostics 0건이었다. accessibility.spec.tsProtectedAdminShell.tsx 단일 fresh diagnostics는 3초 timeout됐지만 npm run typechecknpm run lint 통과로 보완했다.
  • 수동 범위: 현재 자동 검증은 Playwright 설정의 Chromium/mobile Chrome project와 mock mode 기준이다. Edge/Safari/WebKit 실기기 인증은 현재 지원 범위에서 제외한다.

P9-T3 문서·품질 Gate 진행 기록 (2026-07-28):

  • 무엇을: README에 지원 브라우저와 알려진 backend 제약을 추가하고, PRD의 OQ-010 감사 로그 조회 UI를 현재 릴리스 제외로 정렬했다. P9-T3 체크리스트 중 별도 code review와 mock/server 상태 정합성 항목을 제외한 문서 정합성 항목을 완료로 표시했다.
  • 왜: P9-T1/T2의 활성 범위 검증 결과와 EXT-001~011, OQ-009, mock/server 완료 상태가 README·PRD·plan에서 같은 의미로 읽혀야 하기 때문이다.
  • 어떻게: python3 .opencode/skills/ui-ux-pro-max/scripts/search.py "animation accessibility z-index loading" --domain ux -n 12는 12 results를 반환했고, loading feedback, z-index scale, lazy loading, reduced motion, loading button, 150-300ms motion 항목을 확인했다. rg -n "OQ-010 \\| 제외|감사 로그 조회 UI는 현재 릴리스에서 제외|Known Backend Constraints|Browser Support|Edge와 실제 기기|EXT-00[1-7]|EXT-008|EXT-009|EXT-010|EXT-011|UI 확인 완료\\(mock\\)|실제 서버 연동 완료\\(server\\)" README.md docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md는 README/PRD/plan 추적 문자열을 확인했다. git diff --check -- README.md docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md는 exit 0이었다.
  • Markdown link 확인: rg -n "\\[[^\\]]+\\]\\([^\\)]+\\)" README.md docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md로 문서 내 link 목록을 확인했고, 새로 추가한 README 문구에는 신규 link가 없다.
  • Code review: 1차 Oracle review는 OQ-010 섹션 불일치, README backend 제약 누락, P9-T2 browser support 과완료, PRD 수용 기준별 증거 연결 부족을 지적했다. PRD 감사 로그 섹션, README Known Backend Constraints, P9-T2 browser support checklist, P9 수용 기준 증거 요약 표를 수정한 뒤 재검토에서 차단 findings 없음으로 확인했다. 남은 minor는 진행 기록의 제외 항목 문구였고 본 기록에서 정정했다. P9-GATE는 별도 Gate로 남겨 아직 실행하지 않았다.
  • Mock/server 상태: P9 수용 기준 증거 요약과 README Known Backend Constraints를 기준으로 Character/Audio/Series/Community/FanTalk/Comments의 mock UI 완료, server integration 대기, 외부 의존 상태를 분리해 확인했다. P4/P5/P6/P7 server integration Gate 미완료와 P8 Comments 제외/대기는 완료로 표시하지 않는다.
  • 남은 항목: P9-GATE는 별도 실행한다.

P9-GATE 최종 검증 기록 (2026-07-28):

  • 무엇을: 활성 릴리스 범위의 install, typecheck, lint, unit, mock/server E2E, build, 금지 패턴을 최종 Gate로 실행했다. 외부 계약 대기 항목과 server-mode 도메인 integration 미제공 범위는 완료로 승격하지 않았다.
  • 어떻게: npm ci는 307 packages added, 0 vulnerabilities였다. npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다. 최신 build는 235 modules transformed, dist/assets/index-LRzXvNqH.css, dist/assets/index-Bxe7R7m6.js를 생성했다.
  • 자동 검증: npm run test:run은 61 files / 247 tests passed였다. npm run e2e:mock은 최종 재실행에서 188 passed / 20 skipped였다. npm run e2e는 exit 0이었고 server allowlist 36 tests가 실패 없이 종료됐다.
  • 1차 mock E2E 실패와 수정: 320px viewport + 200% zoom overflow는 mobile header breadcrumb가 폭을 밀어서 발생했고, ProtectedAdminShell breadcrumb를 small breakpoint 이상에서만 표시하도록 수정했다. mock shell 중복 GET 검증 실패는 React dev StrictMode의 effect 재실행으로 허용 API GET이 중복 기록된 것이며, request assertion을 중복 제거한 contract set 기준으로 바꿨다.
  • 수정 후 focused 검증: npm run e2e:mock -- tests/e2e/mock-preview-shell.spec.ts --project=chromium은 6 passed, npm run e2e:mock -- tests/e2e/character-workspace.spec.ts --project=chromium은 11 passed, npm run e2e:mock -- tests/e2e/audio-content.spec.ts --project=chromium은 5 passed였다.
  • 금지 패턴 확인: rg -n 'TODO|TBD|FIXME' src testsrg -n "externalCharacterId|SUNDAY|MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|state.?[:=].?['\"]OPEN" src --glob '!**/*.test.*' --glob '!**/*.spec.*' --glob '!**/__tests__/**'는 모두 match 0건이었다.
  • 판정: P9-GATE 명령군은 최종 실행 기준 0 failure/0 error다. P4/P5/P6/P7 server integration Gate 미완료와 P8 Comments 제외/대기는 EXT-004~005, EXT-007~008 및 server-mode testMatch 제약으로 계속 추적한다. EXT-001~002는 legacy 후보 확인, EXT-00999999, EXT-010은 파일 용량·MIME backend 동일 검증, EXT-011은 미정의 오류 기본 문구로 보정됐다.

P9-GATE Oracle 미검증 정정 기록 (2026-07-28):

  • 무엇을: Oracle이 원래 작업 ulw mode requested while executing plan-task.md Phase 3-9 완료 여부를 비판적으로 검토했고 NOT VERIFIED로 판정했다.
  • 왜: P9-GATE 기록은 존재하지만 P4/P5/P6/P7-GATE의 server integration 미완료, P8 Comments 제외/대기, playwright.config.ts server allowlist의 도메인 spec 제외, 구현 완료 정의 미체크 상태가 남아 있어 Phase 3~9 전체 완료로 검증할 수 없기 때문이다.
  • 어떻게: P9 완료 조건의 mock/server 전체 자동 Gate 표현을 활성 mock UI와 server allowlist 자동 Gate로 낮추고, 도메인별 server integration은 backend 계약과 server-mode allowlist 충족 후 별도 완료한다고 정정했다. 현재 판정은 활성 mock UI + P9 자동 Gate 통과, 도메인 server integration 미완료다.

EXT-003 Series 수정 계약 정정 기록 (2026-07-28):

  • 무엇을: EXT-003을 별도 backend 계약 항목에서 제외하고 필요 없음으로 정정했다.
  • 왜: 시리즈 수정 화면은 상세 API가 SeriesListItem과 같은 genreId, enum publishedDaysOfWeek, enum state를 내려주면 기존 선택값을 안전하게 초기화할 수 있고, 장르 API는 option 목록 표시용으로만 호출하면 되기 때문이다.
  • 어떻게: PRD SERIES-01711.5 EXT-003을 필요 없음으로 정정하고, Phase 5의 외부 의존·Gate·P9 요약에서 EXT-003을 제외하도록 갱신했다. 과거 검증 기록의 관련 언급은 이 정정 기록 이후 최신 판정으로 사용하지 않는다.

OpenAPI 2.3.0 계약 보완 및 Phase 10 계획 — 2026-07-29:

  • 무엇을: PRD의 EXT-001, EXT-002, EXT-005, EXT-007, EXT-008을 해결로, EXT-004를 부분 해결로 정정했다. Audio 예약 공개 UTC 전송과 timezone 제거, v2 원작·장르 lookup, Community pagination, FanTalk 답변 수정·팬 원글 삭제, Audio·Community Comments 계약을 요구사항·수용 기준·endpoint 표에 반영하고 Phase 10의 P10-T1~P10-T6, P10-GATE를 추가했다.
  • 왜: OpenAPI 2.3.0이 후속 operation/schema를 제공했고, 사용자 확인으로 FanTalk PUT의 replyId=creatorReplies[].fanTalkId, 댓글 작성자 판정 writerId===creatorId, 팬 작성 FanTalk 원글 DELETE 포함 범위가 확정됐기 때문이다. 완료된 Phase 3~9의 과거 증거는 덮어쓰지 않고 최신 구현을 별도 vertical slice로 추적해야 한다.
  • 어떻게:
    • OpenAPI assertion — jq -e로 version 2.3.0, 원작·장르·FanTalk DELETE implemented, FanTalk reply PUT planned, Audio releaseDate.pattern=Z$, Audio create의 timezone 부재, Community pagination required field, 댓글 operation 10개를 확인해 exit 0이었다.
    • 구조 검증 — Node 검사에서 PRD 요구사항 ID 156개, 중복 0개였고 P10-T1~P10-T6, P10-GATE가 모두 계획 내 2회 이상 추적됐다.
    • 링크 검증 — PRD·plan의 local Markdown link를 파일 시스템과 대조해 broken link 0건이었다.
    • 문구 검증 — 해결된 EXT 6개 상태, creatorReplies[].fanTalkId, writerId === creatorId, UTC Z, Community pagination contract를 rg로 확인했다. Phase 10의 TODO|TBD|FIXME|implement later|위와 동일|적절히 처리는 0건이었다.
    • 문서 검증 — git diff --check -- docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json은 exit 0이었다.
  • 정정: 2026-07-28 P9 기록의 EXT-001~002 legacy 후보, EXT-004~005, EXT-007~008 대기 판정은 당시 이력으로만 보존한다. 2026-07-29 이후 현재 판정은 PRD §11.5와 Phase 10을 우선한다.
  • 남은 항목: 애플리케이션 코드는 이번 문서 보완 범위에서 변경하지 않았다. Phase 10 구현과 FanTalk reply PUT의 실제 server implemented 전환 검증이 남아 있다.

FanTalk PUT 상태·Phase 10 계약 완전성 정정 — 2026-07-29:

  • 무엇을: 백엔드에서 FanTalk 답변 PUT이 이미 구현됐다는 사용자 확인에 따라 OpenAPI operation을 implemented로 정정했다. PRD의 대기 문구를 제거하고 P10-T5의 mock/client/server integration을 모두 활성화했다. 전체 계약 재대조에서 누락된 가격 0..99999, 미정의 오류 공통 문구, 기존 파일 client 검증과 backend 확인 경계를 P10-T7로 추가했으며 댓글 조회의 page·size와 target별 create request, FanTalk PUT 성공 response 처리도 Task interface와 RED에 명시했다.
  • 왜: 이전 Phase 10은 EXT-001~005, EXT-007~008 중심으로만 작성돼 EXT-009~011의 실제 코드 변경·회귀 완료 조건이 없었고, 구현된 FanTalk PUT을 planned로 취급해 server integration을 불필요하게 대기시키고 있었기 때문이다.
  • 어떻게:
    • OpenAPI 검증 — jq -e로 version 2.3.0, 전체 37개 operation의 x-implementation-status=implemented, FanTalk PUT implemented, Audio create/update·Community create price의 minimum=0, maximum=99999, Audio UTC Z와 no-timezone, Community pagination required field를 확인해 exit 0이었다.
    • 요구사항·계획 구조 — Node 검사에서 확정·권고 요구사항 138개, 중복 0개, P10-T1~P10-T7P10-GATE 각 2회 이상 추적을 확인했다. 과거 판단을 보존하는 Phase 10 Decision Log 이전의 실행 Task·Gate 범위에서는 placeholder·오래된 planned 상태가 0건이었다.
    • 문서 연결 — README·PRD·plan의 local Markdown link를 검사해 broken link 0건을 확인했다.
    • 형식 — git diff --check -- README.md docs/20260725_AI캐릭터관리자웹/prd.md docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/api-contract.openapi.json은 exit 0이었다.
  • 정정: 바로 앞 OpenAPI 2.3.0 계약 보완 및 Phase 10 계획 기록의 FanTalk PUT planned, P10-T1~P10-T6, PUT server 전환 대기 판정은 당시 문서 상태의 검증 이력으로만 보존한다. 현재 실행 기준은 OpenAPI의 PUT implementedP10-T1~P10-T7, P10-GATE다.
  • 남은 항목: 애플리케이션 코드는 아직 변경하지 않았다. 현재 확정 요구사항과 제공 operation은 Phase 10 계획으로 구현 가능하다. 계약이 없는 FanTalk 별도 상세/filter/sort·중복 오류 key와 OpenAPI 미포함 인증 정식화는 현재 구현 범위 밖의 비차단 추적 항목이다.

EXT-004 종결·Phase 5/8 실행 계획 보완 — 2026-07-29:

  • 무엇을: 현재 FanTalk UI에 필요하지 않은 별도 상세 GET·답변 상태 filter·sort와 중복 오류 전용 key를 제품 범위에서 제외하고 EXT-004를 해결로 종결했다. Phase 5의 미구현 Series CRUD가 image 계약까지 빠짐없이 P10-T3에서 실행되도록 schema·policy·TDD·검증 항목을 보강하고, Phase 8의 미구현 Comments는 추가 계약 대기 없이 P10-T6에서 구현하도록 최신 판정을 명시했다. Series·FanTalk·Comments의 server E2E spec이 현재 allowlist에서 실행되지 않는 문제도 각 Task와 P10-GATE의 실행 조건에 반영했다.
  • 왜: 목록 item 기반 FanTalk Sheet는 상세 endpoint·filter·sort가 필요 없고, 정확한 중복 오류 key도 client 동작에 사용하지 않는다. 반면 답변 1개 불변식은 UI가 아닌 backend 수용 기준으로 검증해야 한다. Series는 CRUD operation만으로 구현 가능하지만 기존 P10-T3가 생성 필수·수정 optional image와 FILE-005, FILE-015를 Files/RED에 승계하지 않았고, 현재 playwright.config.ts의 server testMatch는 세 도메인 spec을 선택하지 않아 npm run e2e -- <domain spec>이 완료 증거가 될 수 없었다.
  • 어떻게:
    • OpenAPI parse — Series·Comments·FanTalk 관련 24개 operation을 검사해 모두 x-implementation-status=implemented, Comments operation은 정확히 10개임을 확인했다.
    • PRD 구조 — 일반 요구사항 145개, ID 중복 0개, 허용되지 않은 상태 0개를 확인했다. EXT-004=해결, FANTALK-002=제외, FANTALK-007=확정, FANTALK-008=제외 assertion도 통과했다.
    • Phase 10 추적 — SERIES-001~018, COMMENT-001~008, series-image-policy.ts, Series·FanTalk·Comments Chromium server focused 명령과 EXT-004 해결 결정 7개 assertion에 누락이 없었다. Phase 10의 TODO|TBD|FIXME|implement later|위와 동일|적절히 처리는 match 0건이었다.
    • 문서 연결 — PRD·plan의 local Markdown link 38개를 파일 시스템과 대조해 broken link 0건을 확인했다.
  • 정정: 바로 앞 기록의 “계약이 없는 FanTalk 별도 상세/filter/sort·중복 오류 key”는 당시 판정으로 보존한다. 현재 판정은 계약 대기가 아니라 제품 범위 제외이며 EXT-004는 해결이다. 2026-07-28의 P5·P8 대기 체크박스와 검증 기록은 당시 이력으로 유지하고, 현재 구현은 P10-T3·P10-T6에서 진행한다.
  • 남은 항목: 애플리케이션 코드와 playwright.config.ts는 아직 변경하지 않았다. client/mock 구현은 추가 backend 계약 없이 시작할 수 있다. 실제 server 완료에는 전용 개발 API 계정·재사용 가능한 fixture와 server allowlist 변경이 필요하며, 준비되지 않으면 mock 완료와 분리해 server integration 대기로 기록한다.

Phase 3/5/8 체크박스 정합성 정정 — 2026-07-29:

  • 무엇을: 과거 Phase 3의 legacy 원작 lookup 항목, Phase 5 P5-T3·P5-T5의 Series CRUD/form/accessibility 항목, Phase 8 P8-T2~P8-T4의 Comments thread/permission/accessibility 항목을 최신 Phase 10 구현 기준으로 체크 표시했다. §6 구현 완료 정의는 최종 Gate 체크리스트로 분리해 미체크 상태를 유지했다.
  • 왜: OpenAPI 2.3.0 후속 구현이 P10-T1, P10-T3, P10-T6에서 완료됐지만 과거 Phase 체크박스가 남아 있어 실제 구현 상태를 문서에서 구분하기 어려웠기 때문이다. legacy 원작 lookup은 구현하지 않고 v2 searchAiCharacterOriginalWorks로 대체했으며, Phase 8의 과거 계약 부재 상태는 이력으로만 보존한다.
  • 어떻게:
    • 구현 대조: codegraph로 SeriesForm, series-contract.test.ts, CommentThread, comment-contract.test.ts, comment-thread.test.tsx, comments.spec.ts를 확인해 Series v2 장르·CRUD·image policy와 Audio/Community 2단계 댓글·작성자별 action이 존재함을 대조했다.
    • Focused unit: npm run test:run -- src/features/series src/features/comments는 7 files / 21 tests passed였다. 추가로 npm run test:run -- src/features/characters src/features/series src/features/comments는 13 files / 44 tests passed였다.
    • Mock E2E: npm run e2e:mock -- tests/e2e/series.spec.ts tests/e2e/comments.spec.ts --project=chromium --project=mobile-chrome는 19 passed / 1 skipped였다. skipped 1건은 mobile-chrome의 Series keyboard-only 순서 조작 제외 정책이다.
    • Server 분리: npm run e2e -- tests/e2e/comments.spec.ts --project=chromium은 3 passed였다. npm run e2e -- tests/e2e/series.spec.ts --project=chromium은 7 tests가 실제 실행됐지만 login fixture가 /login에 머물러 모두 실패했으므로 Series server integration은 완료로 표시하지 않는다.
    • OQ-009 권고: Series form 권고안은 title 80자, introduction 2,000자, keyword 30자, writer 80자, studio 80자, publishedDaysOfWeek는 실제 요일 최대 7개 또는 RANDOM 단독이다. Comments content 권고안은 2,000자다. backend 호환 확인 전까지 client 상한은 구현하지 않는다.
  • 문서 검증: rg -n "^- \\[ \\]" plan-task.md 결과는 §6 구현 완료 정의 14개만 남았다. git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md는 exit 0이었다. Markdown LSP는 .md 서버 미설정이라 실행할 수 없었다.

Phase 0~10 전체 코드 리뷰·QA — 2026-07-29:

  • 무엇을: PRD, OpenAPI 2.3.0, 구현 계획을 기준으로 Phase 0~10의 현재 working tree를 정적 대조하고 focused unit, 전체 unit, typecheck, lint, production build, 7개 도메인 mock E2E를 실행했다. Phase별 결과는 reviews/review-phase-{0..10}-20260729.md에 각각 분리해 기록했다.
  • 기준: commit dd30e36323543e8f60e9983326503653e8001f12, 리뷰 시작 시 tracked diff 36개·전체 변경 169개, macOS 26.0, Node v24.12.0, npm 11.7.0이다.
  • 판정: Phase 0·2·7은 신규 확정 결함이 없다. Phase 1은 3건, Phase 3은 3건, Phase 4는 6건, Phase 5는 3건, Phase 6은 1건, Phase 8은 2건, Phase 9는 2건, Phase 10은 2건으로 총 22건을 확정했다. 심각도는 High 11건, Medium 10건, Low 1건이며 Blocker는 없다.
  • plan 전환: 확정 항목을 완료 Task 재개방 없이 P1-R6~R7, P3-R1~R2, P4-R1~R3, P5-R1~R2, P6-R1, P8-R1, P9-R1~R2, P10-R1~R2의 신규 goal로 해당 Phase에 추가했다. 각 goal은 연결 리뷰 ID, Files, interface, RED/GREEN/REFACTOR, 실행 명령, 기대 결과와 수동 확인을 포함한다.
  • 성공 검증: focused unit은 shared 27, Character 23, Audio+Series 46, Community+FanTalk+Comments 52 tests가 모두 통과했다. npm run typecheck, npm run lint, npm run build는 exit 0이고 production build는 249 modules transformed를 기록했다.
  • 실패 검증: npm run test:run은 66 files / 290 tests 중 6 failed·284 passed로 종료했다. 실패는 REV-P10-001의 공통 오류 fallback migration 미완료로 추적한다. 7개 spec mock E2E는 병렬 WebKit timeout 4건 때문에 147 passed·17 skipped·4 failed였으나, 실패 4건을 WebKit 단일 worker로 재실행하면 4 passed / 28.4초였다. 이를 재현 가능한 앱 결함으로 추가 확정하지 않되 최초 전체 Gate 실패는 그대로 보존한다.
  • 남은 항목: 위 신규 회귀 goal을 구현하고 전체 unit·mock E2E·server fixture 범위와 P10-GATE를 fresh 재실행해야 한다. 이번 리뷰에서는 애플리케이션 코드와 기존 완료 Task를 수정하지 않았다.

P9-R1 inactive Character 하위 mutation 차단 완료 기록 — 2026-07-30:

  • 무엇을: isActive=false Character workspace에서 Character edit, Audio create/edit/comment, Series create/edit/link/order, Community post/comment mutation affordance와 직접 mutation route를 read-only guidance로 교체했다. 조회/list/detail/player/comment read-only 흐름은 유지했다.
  • 왜: Phase 9 재리뷰 REV-P9-001에서 inactive workspace가 하위 mutation entrypoint를 계속 노출해 backend active-only 검증에만 의존하는 문제가 확인됐기 때문이다.
  • 어떻게:
    • RED: inactive Character 대상 Character edit, Audio create/edit/detail comment, Series create/edit/detail/order, Community sheet/comment 경로의 focused unit/E2E를 추가해 기존 mutation UI/route 노출을 실패로 확인했다.
    • GREEN/REFACTOR: Page 단위 character.isActive와 viewport capability를 분리하고, CommentThread에는 canMutate, CommunityPostSheet에는 canMutateComments를 전달해 post mutation 권한과 comment mutation 권한을 분리했다.
    • Focused unit: npm run test:run -- src/features/characters src/features/audio-contents src/features/series src/features/community-posts src/features/comments는 28 files / 152 tests passed였다.
    • Mock E2E: npm run e2e:mock -- tests/e2e/resource-workflows.spec.ts tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/series.spec.ts tests/e2e/comments.spec.ts는 103 passed / 13 skipped였다. 추가로 320px Community comment 회귀 단일 실행은 1 passed였다.
    • Fresh gate: npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. npm run build production build는 252 modules transformed를 기록했다.
  • 남은 항목: P9-R2 field error 연결과 첫 오류 focus 회귀, 이후 P10-R2, P9-GATE, P10-GATE를 순서대로 진행한다.

P9-R2 field error 연결과 첫 오류 focus 완료 기록 — 2026-07-30:

  • 무엇을: Character, Audio, Series, Community, FanTalk reply, Comment form의 주요 validation error에 stable ID와 aria-describedby/aria-invalid 연결을 추가하고 submit 뒤 DOM 순서 첫 invalid control에 focus하도록 적용했다.
  • 왜: Phase 9 재리뷰 REV-P9-002에서 visible error 문구가 control과 programmatic하게 연결되지 않고 multi-error submit 뒤 첫 오류 focus가 보장되지 않는 문제가 확인됐기 때문이다.
  • 어떻게:
    • RED: 각 도메인 대표 form test에 error description 연결과 첫 invalid focus assertion을 추가해 기존 markup/focus 흐름의 실패를 확인했다.
    • GREEN/REFACTOR: focusFirstInvalidControl(form) helper를 shared로 분리하고, React validation state 반영 뒤 queueMicrotask에서 form 내 첫 aria-invalid="true" control을 focus하도록 최소 적용했다.
    • Focused unit: npm run test:run -- src/features/audio-contents/tests/audio-form.test.tsx src/features/series/tests/series-form.test.tsx src/features/community-posts/tests/community-form.test.tsx src/features/fan-talks/tests/fan-talk-reply.test.tsx src/features/comments/tests/comment-thread.test.tsx는 5 files / 35 tests passed였다. npm run test:run -- src/features는 35 files / 195 tests passed였다.
    • Mock E2E: npm run e2e:mock -- tests/e2e/accessibility.spec.ts는 9 passed / 3 skipped였다. FanTalk/Comments 회귀 npm run e2e:mock -- tests/e2e/fan-talk.spec.ts tests/e2e/comments.spec.ts는 36 passed / 4 skipped였다.
    • Fresh gate: npm run typecheck, npm run lint, npm run build는 모두 exit 0이었다. npm run build production build는 253 modules transformed를 기록했다.
  • 남은 항목: P10-R2, P9-GATE, P10-GATE를 순서대로 진행한다.

P10-R2 PRD OpenAPI 2.3.0 현재 상태 정합성 복구 완료 기록 — 2026-07-30:

  • 무엇을: PRD 현재 계약 설명의 최종 수정일, OpenAPI document version/path/operation 집계, Community pagination metadata 설명을 실제 OpenAPI 2.3.0 상태와 일치시켰다. Phase 10 재리뷰 REV-P10-002 상태도 수정 완료로 정리했다.
  • 왜: PRD 현재형 문장이 OpenAPI 2.0.0 당시의 15 paths·23 operations와 Community metadata 미제공 상태를 계속 설명해 후속 작업자가 현재 계약을 오판할 수 있었기 때문이다.
  • 어떻게:
    • TDD 예외/RED 대체: jq actual 집계가 version=2.3.0 paths=25 operations=37 statuses=implemented임을 확인하고, stale 현재형 문구 검색에서 version 2.0.0, 15개 path, 23개 operation, 종료 metadata 계약이 제공되기 전이 검출되는 것을 확인했다.
    • GREEN: 현재 상태 문장만 2.3.0/25/37과 Community totalCount, page, size, hasNext metadata 제공 상태로 정정했다. 2026-07-28 Decision Log의 과거 이력은 보존했다.
    • Verification: 수정 후 동일 jq 집계는 version=2.3.0 paths=25 operations=37 statuses=implemented였고, current-state stale 검색은 no output이었다. git diff --check -- docs/20260725_AI캐릭터관리자웹는 no output이었다.
  • 남은 항목: P9-GATE, P10-GATE를 순서대로 진행한다.

P9-GATE 재검증 완료 기록 — 2026-07-30:

  • 무엇을: P9-R1, P9-R2, P10-R2 완료 후 활성 릴리스 범위의 install, typecheck, lint, unit, mock/server E2E, build, 금지 패턴을 fresh Gate로 재실행했다. 도메인별 server integration은 전용 개발 API fixture가 필요한 범위와 분리했다.
  • 왜: Phase 9 재리뷰 수정 후 exact npm run e2e:mock와 server allowlist Gate가 0 failure/0 error로 재현되어야 P10-GATE로 넘어갈 수 있기 때문이다.
  • 어떻게:
    • Mock E2E 안정화: comments.spec.ts의 320px Community 댓글 flow가 root 댓글 생성 직후 root reload와 답글 버튼 click이 겹치며 WebKit 첫 실행에서 답글 region을 못 여는 경합을 재현했다. 새 root 댓글 visible, 답글 버튼 enabled, 답글 region visible을 기다리도록 테스트 절차만 최소 보강했다.
    • Server allowlist 정리: bare npm run e2e는 server boundary/auth/smoke/accessibility shell 36 tests만 실행하고, series.spec.ts, fan-talk.spec.ts, comments.spec.ts는 명시 file filter가 있을 때만 server testMatch에 포함되도록 분리했다. 이로써 전용 개발 API fixture 없는 도메인 mock journey가 bare server Gate를 실패시키지 않으며, focused server 명령은 No tests found가 아니게 유지된다.
    • Install/static/unit: npm ci는 307 packages added, 0 vulnerabilities였다. npm run typecheck, npm run lint, npm run test:run, npm run build는 모두 exit 0이었다. 전체 unit은 72 files / 354 tests passed였고 production build는 253 modules transformed, dist/assets/index-B2ehqvd_.js 497.35 kB(gzip 129.53 kB)를 기록했다.
    • E2E: 최종 exact npm run e2e:mock은 206 passed / 22 skipped였다. 직전 full run의 WebKit 계열 /login page.goto timeout 2건은 npm run e2e:mock -- --last-failed --workers=1에서 2 passed로 환경성 timing임을 확인했고, 이후 exact full mock 재실행이 0 failure로 통과했다. npm run e2e는 server allowlist 36 passed였다.
    • 설정 검증: VITE_API_MODE=server npx playwright test --list는 36 tests / 4 files만 listing했고, VITE_API_MODE=server npx playwright test tests/e2e/comments.spec.ts --project=chromium --list는 Comments 3 tests를 listing해 focused domain server spec 교집합을 보존했다.
    • 금지 패턴/형식: rg -n "TODO|TBD|FIXME" src tests, rg -n "externalCharacterId|SUNDAY|MONDAY|TUESDAY|WEDNESDAY|THURSDAY|FRIDAY|SATURDAY|state.?[:=].?['\"]OPEN" src --glob '!**/*.test.*' --glob '!**/*.spec.*' --glob '!**/__tests__/**', git diff --check는 모두 no output이었다.
    • Diagnostics/size: tests/e2e/comments.spec.tsplaywright.config.ts LSP diagnostics는 0건이었다. line count는 playwright.config.ts 40 lines, tests/e2e/comments.spec.ts 133 lines다.
  • 판정: P9-GATE는 최신 exact 명령 기준 0 failure/0 error다. P10 후속 계약 server integration은 P10-GATE에서 mock evidence와 별도로 판정한다.

P10-GATE 검증 기록 — 2026-07-30 (server integration 미완료):

  • 무엇을: OpenAPI 2.3.0 후속 범위의 focused unit, mock E2E, focused server E2E, server allowlist, typecheck, lint, build, diff 검증을 실행했다. mock 성공이나 server spec 일부 통과를 Series/FanTalk 실제 server 완료로 처리하지 않았다.
  • 자동 검증:
    • npm run test:run -- src/features/characters src/features/audio-contents src/features/series src/features/community-posts src/features/fan-talks src/features/comments src/shared/mocks는 37 files / 193 tests passed였다.
    • npm run e2e:mock -- tests/e2e/character-workspace.spec.ts tests/e2e/audio-content.spec.ts tests/e2e/series.spec.ts tests/e2e/community.spec.ts tests/e2e/fan-talk.spec.ts tests/e2e/comments.spec.ts tests/e2e/error-mapping.spec.ts는 155 passed / 17 skipped였다.
    • npm run e2e -- tests/e2e/series.spec.ts tests/e2e/fan-talk.spec.ts tests/e2e/comments.spec.ts --project=chromium은 17 tests 중 Comments 3 passed, Series/FanTalk 14 failed였다. 실패는 모두 login helper가 /login에 머문 Expected /\/ai-characters$/ timeout으로, 전용 개발 API 계정·재사용 fixture 없이 mock journey용 Series/FanTalk spec을 server 완료 증거로 사용할 수 없음을 확인했다.
    • 추가 확인: VITE_API_MODE=server npx playwright test tests/e2e/series.spec.ts tests/e2e/fan-talk.spec.ts --project=chromium --list는 14 tests in 2 files로 Series/FanTalk spec을 정상 수집했다. 두 spec은 admin@test.com/password와 mock fixture ID 101, 5001, 7001, 7002를 그대로 사용한다.
    • 추가 확인: curl -sS -i -X POST "https://test-character-admin.sodalive.net/admin/member/login" ...와 production host 동일 probe는 모두 Could not resolve host로 실패했다. 현재 실행 환경에서는 개발 API host 접근도 불가하므로 credential 유효성 확인까지 진행하지 못했다.
    • npm run e2e는 server allowlist 36 passed였다.
    • npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0이었다. production build는 253 modules transformed, dist/assets/index-B2ehqvd_.js 497.35 kB(gzip 129.53 kB)를 기록했다.
  • 판정: P10-GATE는 focused unit/mock/static/server allowlist와 Comments server focused 범위는 통과했지만, Series/FanTalk focused server integration이 실패했으므로 완료가 아니다. 개발 API host 접근, 전용 ADMIN 계정, 재사용 가능한 fixture ID가 준비되면 npm run e2e -- tests/e2e/series.spec.ts tests/e2e/fan-talk.spec.ts --project=chromium을 다시 실행해 별도 완료 판정한다.
  • 남은 항목: Series active-only·CRUD server 결과, FanTalk 답변 수정·팬 원글 삭제·동시 POST 후 활성 답변 1개 server 결과, 실제 개발 API 파일 용량·MIME 거부 확인.

P10-GATE 자동 범위 정정 및 수동 QA 전환 — 2026-07-30:

P5-R3 Series 표시 label 정합성 복구 진행 기록 — 2026-07-30:

  • 무엇을: Series 목록 table, mobile card, 상세 summary의 요일·state·boolean·genre 표시를 series-display-labels.ts helper로 통일했다. API DTO, form 초기화, create/update payload의 원본 enum·boolean·genreId 값은 유지했다.

  • 왜: Phase 5 리뷰 REV-P5-004에서 운영자 화면에 SUN, PROCEEDING, genreId, isAdult true, isActive true 같은 내부 표현이 노출되는 문제가 확정됐기 때문이다.

  • 어떻게:

    • RED: src/features/series/tests/series-routes.test.tsxtests/e2e/series.spec.ts에 raw enum·boolean·field-name 노출 금지 assertion을 추가했고, 기존 구현에서는 사용자 표시 label 기대가 실패함을 확인했다.
    • GREEN/REFACTOR: formatSeriesPublishedDays, formatSeriesState, formatSeriesGenre, formatSeriesAudience, formatSeriesExposure, formatSeriesFlags를 Series 전용 helper로 분리하고 SeriesList, SeriesListItem, SeriesSummary가 이를 사용하도록 정리했다. 독립 visual QA 1차에서 장르 #id fallback이 내부 ID 노출 가능성으로 지적돼 fallback을 알 수 없는 장르로 바꾸고 lookup miss 회귀 test를 추가했다.
    • Focused unit: npm run test:run -- src/features/series/tests/series-routes.test.tsx는 1 file / 5 tests passed였다. npm run test:run -- src/features/series는 7 files / 29 tests passed였다.
    • Mock E2E: npm run e2e:mock -- tests/e2e/series.spec.ts --project=chromium은 7 passed였다. fallback 수정 후 full npm run e2e:mock -- tests/e2e/series.spec.ts는 25 passed / 3 skipped였다.
    • Fresh static gate: npm run typecheck, npm run lint, npm run build, git diff --check는 모두 exit 0 또는 no output이었다. src/features/series LSP diagnostics는 오류 0건이었다.
    • Browser evidence: mock mode에서 1280px desktop list/detail과 320px mobile list/detail을 fresh capture했고, DOM raw leak check /SUN|PROCEEDING|isAdult true|isActive true|genreId|장르 #/는 네 화면 모두 false였다. PNG signature/dimensions는 정상 확인했다.
    • 수동 확인: 사용자가 Phase 5 현재 리뷰의 재현 및 검증 절차를 보고 수동으로 확인했다.
  • 추가 보완: 독립 visual QA recheck B가 모바일 CJK 줄바꿈과 공통 header의 visible characterId:를 지적해, Series 모바일 안내문·카드 소개문에 break-keep을 적용하고 CharacterWorkspaceLayout header의 characterId: 표시를 제거했다. CharacterWorkspaceLayout.test.tsx는 header characterId: 미노출을 확인한다.

  • 최종 확인: 최종 fresh capture 4개에서 /SUN|PROCEEDING|isAdult true|isActive true|genreId|장르 #|characterId:/ 누출은 모두 false였다. 독립 visual QA 최종 A/B는 모두 PASS, BLOCKING 없음이었다.

  • 남은 항목: P5-R3 범위 없음. Series 실제 server integration은 기존 Gate 정책에 따라 mock UI 증거와 분리해 추적한다.

  • 무엇을: E2E용 ADMIN 계정 미제공 정책에 맞춰 P10-GATE 자동 완료 조건에서 Series/FanTalk 실제 server integration을 제외하고, 같은 Gate의 수동 QA 목록으로 분리했다.

  • 왜: 계정 없는 상태에서 mock credential, sessionStorage 주입, mock 성공을 실제 server 완료 증거로 대체하면 검증 의미가 깨지기 때문이다. 자동 Gate는 focused unit, mock E2E, server allowlist, typecheck, lint, build, diff 검증으로 한정한다.

  • 어떻게: P10-GATE 본문에서 계정 의존 server focused 명령을 자동 명령 목록에서 제거하고, Series active-only·CRUD, FanTalk 답변 수정·팬 원글 삭제·동시 POST reply 1개, 파일 용량·MIME 서버 거부를 수동 QA 체크리스트로 명시했다. Decision Log에는 E2E용 ADMIN 계정을 제공하지 않는 정책과 mock/session 주입을 server 완료 증거로 대체하지 않는 결정을 추가했다.

  • 판정: P10-GATE 자동 Gate는 기존 검증 결과 기준 통과 상태로 정리하고, 실제 개발 API server integration은 수동 QA 대기 항목으로 유지한다.

P8-R2 Comments mock 불변식 수정 검증 — 2026-07-30:

  • 무엇을: Comments mock store의 2단계 parent, AI 작성 row PUT, row-only DELETE 불변식을 복구하고 Audio·Community handler contract test를 추가했다.
  • 왜: 기존 mock이 reply를 부모로 하는 3단계 댓글과 fan 직접 수정을 허용하고, root 삭제 시 직접 답글까지 함께 제거했기 때문이다.
  • 어떻게:
    • RED: npm run test:run -- src/features/comments/tests/comment-contract.test.ts는 1 file / 3 failed / 3 passed로 각 불변식 위반을 재현했다.
    • GREEN: 같은 focused test는 1 file / 6 tests passed였다. npm run test:run -- src/features/comments src/shared/mocks는 8 files / 33 tests passed였다.
    • UI/E2E: npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium은 3 passed, 전체 npm run e2e:mock -- tests/e2e/comments.spec.ts는 10 passed / 2 skipped였다. 기존 UI의 fan PUT 0-request assertion은 유지했다.
    • Static/diff: npm run typecheck, npm run lint, npm run build, git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/phase8-comments.md src/shared/mocks src/features/comments tests/e2e/comments.spec.ts는 모두 exit 0이었다. 변경한 TypeScript 두 파일의 LSP diagnostics도 0건이었다.
  • 남은 항목: 실제 server Comments integration은 별도 P8/P10 Gate 범위다.

Phase 0~10 요청 기준 코드 리뷰·QA — 2026-07-31:

  • 무엇을: prd.md, api-contract.openapi.json 2.3.0, 현재 plan-task.md와 Phase 0~10 구현·test·리뷰 기록을 current working tree에서 다시 대조하고 Phase별 결과를 reviews/phase0-*.md부터 phase10-*.md까지 별도로 누적했다.
  • 왜: 기존 수정 완료 기록과 실제 자동 Gate가 일치하는지 확인하고, 확정된 문제만 완료 Task를 되돌리지 않은 신규 회귀 Task로 전환하기 위해서다.
  • 어떻게:
    • 전체 unit: npm run test:run — exit 1, 78 files 중 2 failed / 76 passed, 394 tests 중 2 failed / 392 passed. 두 실패는 REV-P9-008의 mock E2E script·README·contract test 불일치다.
    • focused 재현: npm run test:run -- src/shared/mocks/__tests__/mode-boundary.test.ts src/shared/mocks/__tests__/mock-preview-docs.test.ts — exit 1, 2 failed / 5 passed.
    • 정적/build: npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, git diff --check, git diff --cached --check — 모두 exit 0. build는 기존 502.94kB chunk warning만 표시했다.
    • server E2E: sandbox listen EPERM을 제품 실패와 분리해 승인된 로컬 실행으로 npm run e2e — 4 projects / 36 passed.
    • mock E2E: npm run e2e:mock — Chromium 52 passed, WebKit 44 passed / 8 skipped, Mobile Chrome 47 passed / 5 skipped, Mobile Safari 43 passed / 9 skipped, 합계 186 passed / 22 skipped / 0 failed.
    • script semantics: npm run e2e:mock -- --list --project=chromium — exit 0, filtered 인자를 전달해 52 tests 수집.
    • OpenAPI: jq parse·version·path/operation·schema ref 검사 — 3.1.0 / 2.3.0, 25 paths / 37 implemented operations, 누락 schema ref 0건.
    • 정적 금지 패턴: production as any, TypeScript 오류 숨김, TODO/TBD/FIXME, externalCharacterId, 민감정보 저장·console 출력 신규 사용을 검색했고 test assertion 외 확정 위반 0건.
  • 판정: 기능·OpenAPI 소유의 신규 결함은 확인되지 않았다. Low 2건을 확정해 REV-P9-008 → 신규 P9-R8, REV-P10-008 → 신규 P10-R7로 전환했다. P10-R7P9-R8 완료 뒤 실행한다.
  • 당시 후속 전환 항목: P9-R8, P10-R7. 현재 남은 항목은 실제 Edge/Safari 기기 최신 2개 major QA와 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA다.

Phase 0~10 요청 최종 재검증 — 2026-07-31:

  • 무엇을: Phase별 현재 리뷰의 마지막 판정을 fresh 전체 Gate와 다시 대조하고, transport 경계를 결합했을 때만 드러나는 인증·upload lifecycle 후보를 별도 진단 test로 검증했다.
  • 자동 Gate: npm run test:run 78 files / 394 tests, 진단 코드 제거 후 기존 fetch/upload auth focused 2 files / 19 tests, npm run e2e 4 projects / 36 passed, npm run e2e:mock 4 projects / 186 passed / 22 skipped / 0 failed. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, OpenAPI parse/version/25 paths/37 implemented operations/모든 $ref, staged·unstaged diff check가 통과했다.
  • 신규 판정: REV-P1-018은 fetch+XHR 동시 401에서 callback 기대 1회 대비 실제 2회, REV-P4-011은 network error→late abort 뒤 새 session 401 callback 기대 총 2회 대비 실제 1회로 재현했다. 진단용 임시 test는 판정 후 제거했고 애플리케이션 코드는 수정하지 않았다.
  • plan 전환: Phase 1 신규 P1-R12와 Phase 4 신규 P4-R8을 추가했다. 두 Task가 upload-audio-content.ts를 함께 수정하므로 P1-R12 완료 뒤 P4-R8을 실행한다.
  • 남은 항목: 실제 Edge/Safari 기기 최신 2개 major QA, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA.

P1-R12·P4-R8 리뷰 보완 완료 — 2026-07-31:

  • 무엇을: fetch client와 Audio XHR upload의 교차 401 만료 전환 중복, upload terminal cleanup 이중 정산을 수정했다.
  • RED/GREEN: P1-R12 RED는 clearSession 기대 2회 대비 실제 3회, reviewer blocker RED는 fetch-first 뒤 XHR 401에서 기대 1회 대비 실제 2회, P4-R8 RED는 새 session 401 callback 기대 2회 대비 실제 1회였다. callback 직전 current token guard와 요청별 settled cleanup 적용 후 focused lifecycle 회귀는 3 files / 22 tests passed였다.
  • 최종 검증: npm run test:run — 79 files / 397 tests passed. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, targeted git diff --check — 모두 exit 0. build는 기존 503.04kB chunk warning만 표시했다. 변경 TS 파일 LSP diagnostics는 오류 0건이고 touched TS files는 모두 250 LOC 이하다.
  • 기록: Phase 1·4 현재 리뷰의 REV-P1-018, REV-P4-011을 수정 완료로 갱신했다. E2E는 사용자 지시에 따라 개발 중 반복 실행하지 않고 실제 개발 API 수동 QA 대기는 유지한다.

Phase 0~10 문서 기준 재리뷰 — 2026-07-31:

  • 무엇을: prd.md, api-contract.openapi.json, plan-task.md, Phase 0~10 현재 리뷰, 제품·test·설정을 current working tree에서 다시 대조하고 결과를 Phase별 review에 별도 누적했다.
  • 정적/OpenAPI: npm run typecheck, npm run lint, npm run build:dev, npm run build:prod는 exit 0이었다. build는 256 modules, 503.04kB JS chunk warning만 표시했다. jq 검사는 OpenAPI 3.1.0, document 2.3.0, 25 paths, 37 operations 전체 implemented, 누락 schema ref 0건을 확인했다.
  • Unit: npm run test:run -- --reporter=verbose — exit 1, 79 files 중 5 failed / 74 passed, 397 tests 중 5 failed / 392 passed, 717.76초. 실패 후보 5개 spec focused는 exit 0, 5 files / 25 tests passed였다. 전체 Gate 비결정성을 REV-P9-009P9-R9로 전환했다.
  • Server E2E: sandbox 실행은 listen EPERM 127.0.0.1:8888로 exit 1이었고, 승인된 로컬 실행 npm run e2e는 Chromium·WebKit·Mobile Chrome·Mobile Safari 36 passed였다.
  • Mock E2E: 승인된 bare npm run e2e:mock은 Chromium 52 passed 후 WebKit main 2 failed / 32 passed / 7 skipped로 exit 1이었고 후속 segment는 실행되지 않았다. 동일 두 실패를 WebKit focused로 재실행한 명령은 exit 0, 2 passed였다. browser Gate 비결정성을 REV-P9-010P9-R10으로 전환했다.
  • 기능·문서 판정: crop frame/저장 영역, Character create/edit 인가 probe, malformed route decode, Community GIF Blob URL, plan 현재형·필수 section 문제를 확정했다. REV-P1-019~021P1-R13~R15, REV-P6-004P6-R4, REV-P10-009~010P10-R8~R9로 전환했다. 제품·test·OpenAPI 코드는 수정하지 않았다.
  • 남은 항목: 신규 P1-R13~R15, P6-R4, P9-R9~R10, P10-R8~R9 구현·회귀 Gate, 실제 Edge/Safari 기기 QA, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA.

리뷰 확정 발견사항 수정 결과 재점검 — 2026-07-31:

  • 무엇을: P1-R13~R15, P6-R4, P9-R9~R10, P10-R8~R9의 구현·문서 수정과 Chromium/mobile Chrome 제품 결정을 current staged working tree에서 다시 대조했다.
  • 왜: 완료 기록을 실제 코드·test·실행 가능한 검증 명령으로 재현하고 남은 문제만 기존 완료 Task를 열지 않는 신규 회귀 Task로 전환하기 위해서다.
  • 어떻게:
    • Phase 1 focused 5 files / 46 tests, Phase 6 focused 3 files / 18 tests, Phase 9 docs contract 2 files / 7 tests가 통과했다.
    • 전체 npm run test:run은 두 차례 연속 각각 81 files / 409 tests passed였다. npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, staged diff check도 exit 0이었다. build는 기존 500kB chunk warning만 표시했다.
    • npm run e2e:mock -- --list는 Chromium/mobile Chrome 104 tests, npm run e2e -- --list는 같은 두 project 18 tests만 수집했다. Character/FanTalk focused mock E2E는 두 project 합계 4 passed였고, Phase 1 crop·Phase 6 Community Gate의 Chromium 4-spec 실행은 31 passed였다.
    • OpenAPI는 3.1.0 / document 2.3.0, 25 paths / 37 operations, 누락 schema ref 0건이었다. Goal 필수 section 12/12와 26개 Markdown 파일의 local broken link 0건을 확인했다.
  • 판정: REV-P1-022 malformed route 보호 probe 우회, REV-P9-011 Chromium-only 문서·Decision Log·smoke 경로 불일치, REV-P10-011 §5.1 현행화 누락과 self/history match 검증 명령을 확정해 각각 신규 P1-R16, P9-R11, P10-R10으로 전환했다. 제품·test·설정 코드는 수정하지 않았다.
  • 남은 항목: P1-R16P9-R11P10-R10 구현·문서 검증, 실제 crop pixel·stale ADMIN server QA와 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA.

P1-R16·P9-R11·P10-R10 수정 결과 재점검 — 2026-07-31:

  • 무엇을: 세 회귀 Task의 unstaged 제품·test·문서 수정과 현재 Chromium/mobile Chrome Gate를 코드 흐름, focused/full test와 원문 검증 명령으로 재검토했다.
  • 자동 검증: 관련 6 files / 42 tests, 전체 81 files / 411 tests, typecheck, lint, 개발/운영 build와 staged·unstaged diff check가 통과했다. mock list 104 tests와 server list 18 tests는 Chromium/mobile Chrome 두 project만 수집했다. build는 기존 500kB chunk warning만 표시했다.
  • 문서 검증: P10의 동작하도록 바로잡은 section-aware 검색은 0 matches였지만, 문서 원문의 이중 escape 명령은 section을 닫지 못해 과거 이력 10건을 다시 매치했다. local Markdown link는 26 files / broken 0이었다.
  • 판정: 제품 route guard의 신규 runtime 실패는 확인되지 않았다. REV-P1-023 인가 matrix·focused Gate 누락, REV-P9-012 Chromium-only 문서 종료 증거 누락, REV-P10-012 원문 명령·현재 상태 재현 실패를 확정해 각각 P1-R17, P9-R12, P10-R11로 전환했다.
  • 남은 항목: P1-R17P9-R12P10-R11, 실제 crop pixel·stale ADMIN server QA와 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA. WebKit·Mobile Safari는 지원·검증 범위에서 제외한다.

P1-R17·P9-R12·P10-R11 수정 결과 재점검 — 2026-07-31:

  • 무엇을: 세 회귀 Task의 코드·test·문서 수정과 Chromium/mobile Chrome 수집 범위를 current working tree에서 재검토했다.
  • 자동 검증: 관련 6 files / 48 tests, 전체 81 files / 417 tests, typecheck, lint, 개발/운영 build와 staged·unstaged diff check가 통과했다. mock list 104 tests와 server list 18 tests는 Chromium/mobile Chrome 두 project만 수집했다. build는 기존 500kB chunk warning만 표시했다.
  • 문서·계약 검증: P10 원문 section-aware negative search와 dependency·필수 12 section·local Markdown link 검사는 exit 0이었다. OpenAPI는 3.1.0 / document 2.3.0, 25 paths / 37 operations 전체 implemented, 누락 schema ref 0건이었다.
  • Phase별 판정: Phase 1 malformed route 인가 matrix에는 신규 문제가 없었다. REV-P9-013 Decision Log 사유·문서 section scope 미종결과 REV-P10-013 최신 Progress/current-state 불일치를 Low로 확정해 각각 신규 P9-R13, P10-R12로 전환했다. 제품·Playwright 설정·OpenAPI는 수정하지 않았다.
  • 남은 항목: P9-R13P10-R12, 실제 crop pixel·stale ADMIN server QA와 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA. WebKit·Mobile Safari는 지원·검증 범위에서 제외한다.

P9-R13·P10-R12 수정 결과 재점검 — 2026-07-31:

  • 무엇을: P9-R13의 Chrome-only Decision Log·section contract와 P10-R12의 top/tail current-state·Phase 10 리뷰 metadata를 최신 상태로 정렬했다.
  • 자동 검증: docs contract 2 files / 8 tests, 전체 unit, typecheck, lint, 개발/운영 build, Markdown link 검사와 git diff --check를 통과했다. mock list 104 tests와 server list 18 tests는 Chromium/mobile Chrome 두 project만 수집했다.
  • 문서 판정: P1-R17, P9-R12, P10-R11, P9-R13, P10-R12 자동 보완은 완료됐고, 과거 Progress·Decision·finding은 이력으로 보존했다.
  • 남은 항목: 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA. WebKit·Mobile Safari는 지원·검증 범위에서 제외한다.

P9-R13·P10-R12 수정 결과 재리뷰 — 2026-07-31:

  • 무엇을: Chrome-only Decision Log·docs contract, plan top/tail·Task checklist와 Phase 10 리뷰 상태를 원문·negative-control·fresh Gate로 다시 대조했다.
  • 자동 검증: docs contract 2 files / 8 tests, typecheck, lint, 개발/운영 build, OpenAPI parse·status/ref와 staged·unstaged diff check가 통과했다. mock list 104 tests와 server list 18 tests는 Chromium/mobile Chrome 두 project만 수집했다.
  • full unit: 첫 fresh 실행은 Community Sheet 취소 후 focus가 trigger 대신 닫기로 복귀해 1 failed / 416 passed였고, 동일 test 단독 5회는 모두 통과했다. 두 번째 fresh full은 81 files / 417 tests passed해 full-load effect 경합으로 판정했다.
  • Phase 9 판정: REV-P9-014 full unit focus 반환 비결정성을 Medium, REV-P9-015 PRD Decision Log·최신 종료 판정의 후속 H2/H3 false positive를 Low로 확정해 P9-R14~R15로 전환했다.
  • Phase 10 판정: REV-P10-014 top/checklist와 tail/review 완료 상태 충돌 및 OR 기반 검증 false positive를 Low로 확정해 P10-R13으로 전환했다. 제품·Playwright 설정·OpenAPI는 수정하지 않았다.
  • 남은 항목: P9-R14P9-R15P10-R13, 실제 crop pixel·stale ADMIN server QA와 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA. WebKit·Mobile Safari는 지원·검증 범위에서 제외한다.

P10-R13 수정 검증 기록 — 2026-07-31:

  • 무엇을: P9-R14, P9-R15, P10-R13 순서로 자동 보완 Task를 완료하고 plan top/checklist/latest Progress/Phase 10 review를 같은 현재 상태로 맞췄다.
  • 검증: docs contract 2 files / 9 tests, 전체 unit 81 files / 418 tests, typecheck, lint, build:dev, build:prod, 필수 12 section 검사와 git diff --check가 통과했다. build는 기존 500kB chunk warning만 표시했다.
  • 남은 항목: 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA. WebKit·Mobile Safari는 지원 범위에서 제외하고 Chromium/mobile Chrome만 유지한다.

P9-R14·P9-R15·P10-R13 수정 결과 재점검 — 2026-07-31:

  • 무엇을: 세 Task의 변경, 최신 Phase 9·10 review와 docs contract를 exact scope negative-control 및 fresh 자동 Gate로 재검토했다.
  • 자동 검증: finding 기록 전 Community Sheet focused 5회는 각각 1 passed / 6 skipped, docs contract는 2 files / 9 tests, fresh full unit은 81 files / 418 tests였다. typecheck, lint, build:dev, build:prod가 통과했고 Playwright --list는 Chromium/mobile Chrome에서만 mock 104 tests와 server 18 tests를 수집했다. finding과 신규 Task를 current-state에 반영한 뒤 docs contract는 기존 자동 보완 완료 기대를 실패시켜 1 failed / 8 passed RED가 됐다.
  • Phase 9 판정: focus test 수정은 재현됐다. sectionAtHeading()의 임의 suffix 허용과 과거 H2 대상 고정을 REV-P9-016 Low로 확정해 신규 P9-R16으로 전환했다.
  • Phase 10 판정: plan 상단 Progress 누락, 최신 Progress의 EOF scope, review metadata·종료 판정 미분리를 REV-P10-015 Low로 확정해 P9-R16 뒤 신규 P10-R14로 전환했다.
  • 남은 항목: P9-R16P10-R14, 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA. WebKit·Mobile Safari는 실행하지 않았고 Chromium/mobile Chrome 지원 범위만 유지한다.

P9-R16·P10-R14 수정 결과 재점검 — 2026-08-01:

  • 무엇을: exact heading·Progress helper와 negative-control, plan top/§7 Progress, Phase 9·10 current metadata·최신 결론을 current working tree에서 재검토했다.
  • 자동 검증: finding 기록 전 docs contract 2 files / 9 tests, 전체 unit 81 files / 418 tests, typecheck, lint, 필수 12 section·Markdown link·git diff --check가 통과했다. Playwright --list는 Chromium/mobile Chrome에서만 mock 104 tests와 server 18 tests를 수집했다. 신규 finding과 Task를 current-state에 반영한 뒤 docs contract는 기존 자동 보완 완료 기대를 실패시켜 1 failed / 8 passed RED가 됐다.
  • Phase 9 판정: exact heading·H3 경계 수정은 확인했다. Phase 9 metadata와 고정된 ## 24 결론이 완료된 P10-R14를 후속으로 유지하고 trailing H2를 놓치는 문제를 REV-P9-017 Low로 확정해 신규 P9-R17로 전환했다.
  • Phase 10 판정: contract가 current Task checklist와 §7 em-dash형 Progress 대신 과거 checklist·Task-local 괄호형 기록을 검사하고, 최하단은 P9-R16P10-R14 미완료를 유지하며 검증 H2 날짜도 실행일과 다른 문제를 REV-P10-016 Low로 확정해 P9-R17 뒤 신규 P10-R15로 전환했다.
  • 남은 항목: P9-R17P10-R15, 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA. WebKit·Mobile Safari는 실행하지 않았고 Chromium/mobile Chrome 지원 범위만 유지한다.

P9-R17·P10-R15 수정 검증 기록 — 2026-08-01:

  • 무엇을: P9-R17, P10-R15 순서로 Phase 9 metadata·실제 마지막 H2와 Phase 10 §7 Progress marker/current-state contract를 보완했다.
  • 자동 검증: focused RED/GREEN 뒤 docs contract 2 files / 9 tests, 전체 unit 81 files / 418 tests, typecheck, lint, 필수 12 section·Markdown link·git diff --check가 통과했다. Playwright는 사용자 지시에 따라 실제 E2E 대신 Chromium/mobile Chrome --list만 수집했으며 mock 104 tests, server 18 tests였다.
  • 판정: 자동 보완 Task는 완료됐다. 남은 항목은 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA다. WebKit·Mobile Safari는 실행하지 않았고 Chromium/mobile Chrome 지원 범위만 유지한다.

P9-R17·P10-R15 수정 결과 재점검 — 2026-08-01:

  • 무엇을: 두 Task의 변경과 Phase 9·10 current review, docs contract helper를 current working tree와 synthetic mutation으로 다시 대조했다.
  • 수정 확인: finding 기록 전 docs contract 2 files / 9 tests와 전체 unit 81 files / 418 tests가 통과했고 typecheck, lint, 필수 12 section·Markdown link·git diff --check도 exit 0이었다. Playwright --list는 Chromium/mobile Chrome에서만 mock 104 tests와 server 18 tests를 수집했다.
  • current-state 전환: 신규 finding·Task를 반영한 docs contract는 2 files 중 1 failed / 1 passed, 9 tests 중 2 failed / 7 passed의 의도한 RED다. 실패는 P9-R17 범위에 후속 미완료 Task가 섞이는 문제와 완료 상태만 기대하는 Phase 10 current-state assertion에서 발생했다. 필수 section 12/12, 26개 Markdown 파일 local link broken 0, git diff --check는 통과했다.
  • Phase 9 판정: P9-R17 수정은 확인했다. 완료 finding 상태·Task 범위 불일치 REV-P9-018과 fenced heading·동일 제목 최신 H2 오인 REV-P9-019를 Low로 확정해 P9-R18P9-R19로 전환했다.
  • Phase 10 판정: P10-R15 수정은 확인했다. 완료 finding 상태·자체 checklist·실제 최신 §7 Progress 누락을 REV-P10-017 Low로 확정해 P10-R16으로 전환했다. 제품·OpenAPI·Playwright 설정의 신규 문제는 확인되지 않았다.
  • 남은 항목: P9-R18P9-R19P10-R16, 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA. WebKit·Mobile Safari는 실행하지 않았고 Chromium/mobile Chrome 지원 범위만 유지한다.

P9-R18~P9-R19·P10-R16 수정 검증 기록 — 2026-08-01:

  • 무엇을: Phase 9 finding·Task 경계와 최신 H2 탐색, Phase 10 finding·checklist·실제 마지막 독립 Progress contract를 순서대로 보완했다.
  • RED/GREEN: P9-R18 finding 상태 focused RED, P9-R19 fenced heading·동일 제목 2건 RED/GREEN, P10-R16 고정 marker synthetic RED/GREEN과 current-state RED를 확인했다. 새 parser나 dependency 없이 기존 문자열 helper만 최소 확장했다.
  • 자동 검증: Phase 9 focused 3 passed / 5 skipped, Phase 10 focused 2 passed / 7 skipped, docs contract 2 files / 12 tests passed였다.
  • 전체 검증: npm run test:run은 81 files / 421 tests passed, npm run typechecknpm run lint는 exit 0이었다. 필수 section 12/12, 26개 Markdown 파일 local link broken 0, git diff --check도 통과했다. Playwright --list는 Chromium/mobile Chrome에서만 mock 104 tests와 server 18 tests를 수집했다.
  • 판정: P9-R18, P9-R19, P10-R16 자동 보완 Task는 완료됐다. 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA는 별도 대기다. WebKit·Mobile Safari는 실행하지 않고 Chromium/mobile Chrome 지원 범위만 유지한다.

P10-R16 수정 결과 재점검 — 2026-08-01:

  • 무엇을: P10-R16의 실제 마지막 Progress 선택을 독립 reviewer가 current helper와 동일 marker synthetic 관점에서 재검토했다.
  • 판정: 마지막 marker 문자열을 첫 동일 occurrence로 다시 찾는 REV-P10-018 Low를 확정해 신규 P10-R17로 전환했다. 나머지 finding/checklist/H2/top-tail 동기화에는 신규 문제가 없었다.
  • 남은 항목: P10-R17, 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA. Chromium/mobile Chrome 지원 범위만 유지한다.

ImageCropDialog advanced cropper 전환 검증 기록 — 2026-08-04:

  • 무엇을: 공통 ImageCropDialog의 custom 이동·zoom UI를 react-advanced-cropper@0.20.1로 교체하고, 기존 aspect, maxWidth, noUpscale, renderCrop 계약과 pending/error/single-flight 처리를 유지했다. 사용자의 명시적 결정에 따라 별도 방향·zoom 버튼은 제거하고 pointer와 keyboard 조작만 유지하도록 PRD FILE-008과 UI 원칙을 정렬했다.
  • TDD/회귀: viewport·aspect·keyboard·null coordinates·orientation request·focus wrapper를 각각 RED/GREEN으로 확인했다. 독립 리뷰가 제기한 비중앙 좌표 이중 축척 후보는 scalePreviewOffset이 원본 크기가 아니라 baseWidth/baseHeight를 사용함을 확인했고, off-center 1:1과 210:297 좌표를 calculateCropSourceRect로 되돌리는 회귀 2건을 추가해 선택 source rect가 일치함을 검증했다. 적용 중 Escape가 disabled 취소 버튼을 우회하는 RED도 재현해 pending 동안 Escape를 무시하도록 정렬했다.
  • 자동 검증: npm run test:run은 82 files / 438 tests passed, npm run typecheck, npm run lint, npm run build:dev, npm run build:prod, git diff --check는 exit 0 또는 no output이었다. dev/prod build는 309 modules, JS 597.35kB(gzip 158.54~158.55kB)와 기존 500kB chunk warning을 기록했다.
  • 브라우저 QA: npm run e2e:mock -- tests/e2e/series.spec.ts --project=chromium은 7 passed였다. mock UI 768/1280px에서 pointer drag, 방향키, +/-, 초기화, 취소/Escape, focus trap, 적용과 가로 overflow 0을 확인했고, 2400×1804 입력은 210:297 비율의 1000×1414px PNG로 생성됐다. 375px Series 생성은 기존 모바일 read-only 정책에 따라 crop form을 노출하지 않는다.
  • 공급망: npm audit --omit=dev는 vulnerability 0건이다. 전체 npm audit의 high 1건은 ESLint가 사용하는 dev-only brace-expansion@5.0.8 경로이며 production cropper dependency에는 포함되지 않는다.
  • 남은 항목: 실제 개발 API 파일 업로드 수동 QA는 기존 server integration 대기로 유지한다. 375px Series 안내의 기존 오탈자·줄바꿈은 crop surface 밖의 별도 UI 정리 범위다.

P10-R17 수정 검증 기록 — 2026-08-01:

  • 무엇을: 동일 제목·날짜 Progress marker가 반복돼도 마지막 occurrence의 record를 선택하도록 보완했다.
  • RED/GREEN: 중복 marker synthetic은 1 failed / 8 skipped에서 1 passed / 8 skipped로 전환했고 current-state contract에 P10-R17 checklist·finding·최신 review를 포함했다.
  • 자동 검증: docs contract 2 files / 12 tests, 전체 unit 81 files / 421 tests, typecheck, lint, 필수 section 12/12, 26개 Markdown 파일 local link broken 0과 git diff --check가 통과했다. Playwright --list는 Chromium/mobile Chrome에서만 mock 104 tests와 server 18 tests를 수집했다.
  • 독립 재리뷰: 직전 동일 marker Important가 닫혔고 신규 Critical/Important/Minor 없음, 요청 범위 merge ready로 판정됐다.
  • 판정: P9-R18P9-R19, P10-R16P10-R17 자동 보완 Task는 완료됐다. 실제 crop pixel 비교, stale ADMIN server 확인, 실제 개발 API Series/FanTalk/Comments/file policy 수동 QA는 별도 대기다. Chromium/mobile Chrome 지원 범위만 유지한다.