19 KiB
Phase 5 Series Management 코드 리뷰·QA
1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 5 / Series CRUD, 장르, 콘텐츠 연결·해제·순서 |
| 기준 commit 또는 working tree | dd30e36323543e8f60e9983326503653e8001f12, 2026-07-31 종합 재점검 당시 사용자 변경을 포함한 current working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | prd.md, api-contract.openapi.json 2.3.0, plan-task.md |
| 리뷰 상태 | 판정 완료, 신규 회귀 수정 완료 |
2. 리뷰 목적과 범위
목적
- Series CRUD·lookup·연결·순서 payload와 responsive UI가 요구사항·계약에 맞는지 검증한다.
- API 원본 enum/ID와 사용자 표시 label의 경계가 일관적인지 확인한다.
포함 범위
- 코드:
src/features/series - 테스트: Series contract/form/route/order/E2E
- 문서:
SERIES-001~SERIES-018, 관련 OpenAPI operation,P5-* - 수동 검증: list/card/detail에서 같은 DTO의 표시 결과 정적 대조
제외 범위
- 실제 서버의 전체 page 순서 저장 fixture
- Phase 0 origin 문제로 실행되지 못한 server E2E의 중복 판정
3. 판정 기준
심각도는 Blocker, High, Medium, Low, 상태는 후보, 확정, 오탐, 보류, 수정 완료를 사용한다. 계약 payload 무결성과 운영자에게 노출되는 label 일관성을 중점 확인했다.
4. 검토한 근거
문서와 코드
- 요구사항:
SERIES-002,SERIES-005,SERIES-007,SERIES-015, PRD §13 - 계약: Series list/detail item, genre lookup, CRUD·link·unlink·order operations
- 계획:
P5-T1~P5-GATE - 코드:
SeriesList.tsx:8-38,SeriesListItem.tsx:5-28,SeriesSummary.tsx:3-35 - 테스트:
series-routes.test.tsx:147등 raw label 기대
실행 환경
macOS 26.0 / Node v24.12.0 / npm 11.7.0
Playwright 4 projects, 320/768/1280px
실행한 검증
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|---|---|---|
npm run test:run |
성공 | 72 files, 354 tests passed |
npm run e2e:mock |
부분 실패 | Series 전체 흐름은 WebKit 768px 1회 timeout 외 통과 |
npm run e2e:mock -- tests/e2e/series.spec.ts --project=webkit --grep "desktop and tablet Series management flow remains available at 768px" |
성공 | 1 passed, 12.6s; 결함으로 확정하지 않음 |
npm run typecheck / npm run lint / npm run build |
성공 | 모두 exit 0 |
| 표시 label 정적 대조 | 실패 | 목록·card는 raw day/state/genreId/boolean, 상세도 raw genreId/boolean |
| 생성·수정 schema 2차 계약 대조 | 실패 | 누락 genreId와 중복 publishedDaysOfWeek가 parsing·mock create에서 허용됨 |
5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-P5-004 |
Medium | 수정 완료 | Series 목록·card·상세가 enum·genreId·boolean 원본을 운영자에게 노출한다 | P5-R3 |
P5-R3 |
REV-P5-005 |
Medium | 수정 완료 | Series schema와 mock이 필수 장르·요일 유일성 불변식을 강제하지 않는다 | P5-R4 |
P5-R4 |
6. 발견 사항 상세
REV-P5-004 — Series 목록·card·상세가 enum·genreId·boolean 원본을 운영자에게 노출한다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항:
SERIES-002,SERIES-005,SERIES-007,SERIES-015, PRD §13 - 관련 계약: Series 원본 enum/ID는 수정 payload에 유지
- 소유 Task: 신규
P5-R3
관찰 내용
desktop list와 mobile card는 요일·state enum을 그대로 표시하고 genreId 77, isAdult false, isActive true 같은 내부 필드명을 출력한다. 상세는 요일·state만 변환하며 genreId와 boolean은 그대로 표시한다. 같은 DTO가 surface마다 서로 다른 label 정책을 사용한다.
근거
- 코드:
src/features/series/components/SeriesList.tsx:8-38 - 코드:
src/features/series/components/SeriesListItem.tsx:5-28 - 코드:
src/features/series/components/SeriesSummary.tsx:3-35 - 테스트:
src/features/series/tests/series-routes.test.tsx:147이genreId 77노출을 기대 - 문서:
prd.md:283SERIES-015,prd.md:806
재현 또는 검증 절차
- Series 목록을 desktop과 mobile width에서 연다.
- 요일
SUN/RANDOM, 상태PROCEEDING,genreId,isAdult false가 표시되는 것을 확인한다. - 상세에서는 일부만 한글 label로 바뀌어 surface 간 표현이 다른 것을 확인한다.
- 요구 결과는 API 원본을 payload에만 유지하고 표시에는 장르명·요일·상태·boolean label을 일관되게 사용하는 것이다.
영향
운영자가 API 내부 표현을 해석해야 하고, desktop/mobile/detail 사이에서 같은 시리즈 상태가 다르게 보인다.
권장 조치
장르 lookup map과 중앙 Series formatter를 사용해 모든 surface의 표시를 통일하되 form 초기화·update payload에는 기존 원본 enum과 ID를 보존한다.
판정 기록
- 2026-07-30 — PRD 표시 경계와 세 UI surface 및 고정 test를 대조해 확정.
- 2026-07-30 —
P5-R3구현과 fallback/CJK/header 보완 후 focused unit, full Series mock E2E, static gate, 수동 확인, 독립 visual QA 최종 A/B PASS로 수정 완료 판정.
REV-P5-005 — Series schema와 mock이 필수 장르·요일 유일성 불변식을 강제하지 않는다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항:
SERIES-006,SERIES-007 - 관련 계약:
SeriesCreateRequest.publishedDaysOfWeek.uniqueItems=true,genreId=0은 domain에서 무효 - 소유 Task: 신규
P5-R4
관찰 내용
seriesCreateRequestSchema의 genreId가 optional이고 공용 publishedDaysSchema에는 중복 검사가 없다. 따라서 생성 schema는 장르가 없는 payload와 ["MON","MON"]을 모두 허용한다. mock create는 장르가 없으면 첫 fixture 장르를 임의로 넣어 잘못된 request를 성공 처리한다. 화면 form은 장르 선택과 요일 toggle을 제한하지만 API adapter·mock 경계는 같은 불변식을 보장하지 않는다.
근거
- 코드:
src/features/series/schemas/series-schema.ts:15-26 - mock:
src/shared/mocks/series-mock-store.ts:48-55 - 실행 재현:
seriesCreateRequestSchema.safeParse()결과 누락 장르true, 중복 요일true - 문서:
prd.md:274-275 - 계약: OpenAPI
SeriesCreateRequest.publishedDaysOfWeek.uniqueItems=true,genreId설명의 domain validation
재현 또는 검증 절차
{title,introduction,publishedDaysOfWeek:["MON"],keyword}를 create schema에 넣으면 성공한다.genreId:1,publishedDaysOfWeek:["MON","MON"]을 넣어도 성공한다.- 같은 누락 장르 multipart를 mock POST에 보내면 mock store가 첫 장르로 보정해 생성한다.
- 요구 결과는 누락/0 장르와 중복 요일을 request 경계에서 거부하는 것이다.
영향
현재 form 경로 밖의 adapter 호출이나 회귀 test가 계약 밖 Series를 전송할 수 있고, mock preview가 이를 정상 생성으로 숨긴다. update schema도 같은 요일 schema를 재사용하므로 중복 요일을 허용한다.
권장 조치
create genreId를 positive integer 필수로 만들고 공용 요일 schema에 uniqueness를 추가한다. mock의 첫 장르 fallback을 제거하고 schema·contract test에서 invalid request를 고정한다.
판정 기록
- 2026-07-30 — PRD·OpenAPI와 schema 실행 결과·mock fallback을 대조해 확정.
- 2026-07-30 —
P5-R4에서 생성genreId필수와 생성·수정 요일 유일성을 schema에 적용하고 mock의 첫 장르 fallback을 제거했다. focused Series 단위 회귀와 정적 Gate로 수정 완료를 확인했다.
7. 확정 항목의 plan·goal 전환
REV-P5-004→plan-task.md신규P5-R3- goal objective:
[P5-R3] Series 표시 label을 중앙화하고 API 원본값과 UI 표현의 경계를 복구한다. REV-P5-005→plan-task.md신규P5-R4- goal objective:
[P5-R4] Series 필수 장르와 요일 유일성을 schema·mock 경계에서 복구한다.
8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | Series 코드·test·계약 대조 |
| 후보 항목 판정 완료 | 충족 | 기존 1건 수정 완료, 신규 1건 수정 완료 |
| 확정 항목 plan 반영 | 충족 | P5-R3, P5-R4 완료 |
| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 없음 |
| 검증 명령과 결과 기록 | 충족 | §4 |
최종 결론: 확정 발견 사항 수정 완료.
남은 항목: Series 일반 mutation pending 경계는 REV-P9-004/P9-R4, 실제 server integration은 기존 수동 QA 정책으로 분리한다.
9. 수정 후 검증 기록
- 2026-07-30 — RED:
src/features/series/tests/series-routes.test.tsx와tests/e2e/series.spec.ts에 desktop/mobile 목록·상세의 raw enum·boolean·field-name 노출 금지 assertion을 추가했다. 기존 raw 표시 기대를 운영자용 label 기대와 no-raw assertion으로 바꾸는 과정에서REV-P5-004재현 조건을 고정했다. - 2026-07-30 — GREEN/REFACTOR:
src/features/series/lib/series-display-labels.ts를 추가해 요일, state, genre, 성인 여부, 노출 상태 label을 중앙화하고SeriesList,SeriesListItem,SeriesSummary가 이를 사용하도록 정리했다.SeriesListPage와SeriesDetailPage는 장르 lookup을 함께 불러와 표시 계층에 전달하며, form·mutation payload는 기존 원본 enum/ID를 유지한다. - 2026-07-30 — 독립 리뷰 1차: code review는 PASS, visual QA B는 PASS였다. visual QA A는
formatSeriesGenre()의장르 #idfallback이 lookup 실패 시 내부 ID를 표시할 수 있다고 지적해 REVISE를 반환했다. - 2026-07-30 — fallback 보완:
formatSeriesGenre()fallback을알 수 없는 장르로 바꾸고, 장르 lookup miss에서장르 #,genreId,77이 표시되지 않는 route 회귀 test를 추가했다. - 2026-07-30 — 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. - 2026-07-30 — E2E/static:
npm run e2e:mock -- tests/e2e/series.spec.ts --project=chromium결과 7 passed. fallback 수정 후npm run e2e:mock -- tests/e2e/series.spec.ts결과 25 passed / 3 skipped.npm run typecheck,npm run lint,npm run build,git diff --check는 모두 exit 0 또는 no output이었다. - 2026-07-30 — 수동 확인: mock mode에서 1280px desktop list/detail과 320px mobile list/detail fresh capture 및 DOM raw leak check를 수행했고
/SUN|PROCEEDING|isAdult true|isActive true|genreId|장르 #/는 네 화면 모두 false였다. 사용자가 이 문서의 재현 및 검증 절차를 보고 수동 확인했다. - 2026-07-30 — visual recheck B 보완: 모바일 안내문·카드 소개문의 CJK 중간 줄바꿈을 막기 위해
break-keep을 적용하고, 공통 workspace header의 visiblecharacterId:표시를 제거했다.CharacterWorkspaceLayout.test.tsx는 header의characterId:미노출을 확인한다. - 2026-07-30 — 최종 검증:
npm run test:run -- --fileParallelism=false src/layouts/CharacterWorkspaceLayout.test.tsx src/features/series/tests/series-routes.test.tsx결과 2 files / 10 tests passed.npm run test:run -- src/features/series결과 7 files / 29 tests passed.npm run e2e:mock -- tests/e2e/series.spec.ts결과 25 passed / 3 skipped.npm run typecheck,npm run lint,npm run build,git diff --check는 모두 통과했다. - 2026-07-30 —
P5-R4RED:npm run test:run -- src/features/series/tests/series-contract.test.ts결과 1 failed / 6 passed로genreId누락 create payload 허용을 재현했다. - 2026-07-30 —
P5-R4GREEN/회귀:seriesCreateRequestSchema의genreId를 필수 positive integer로 바꾸고 공용 요일 schema에 uniqueness를 추가했으며, mock create의 첫 장르 fallback을 제거했다.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,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이었다. 개발 중 E2E는 사용자 지시에 따라P10-R5이후 최종 E2E로 미뤘다. - 2026-07-30 — 최종 visual QA: 1280px desktop list/detail, 320px mobile list/detail fresh capture에서
/SUN|PROCEEDING|isAdult true|isActive true|genreId|장르 #|characterId:/누출 0건을 확인했다. 독립 visual QA 최종 A와 B는 모두 PASS, BLOCKING 없음이었다. - 2026-07-30 — 2차 계약 점검에서 누락
genreId와 중복 요일의 schema parse 성공 및 mock 첫 장르 fallback을 재현했다. 애플리케이션 코드는 수정하지 않고P5-R4로 전환했다.
10. 2026-07-31 재점검
실행·판정 요약
- 범위: Series CRUD·장르·요일·연결·순서·image lifecycle을 PRD
SERIES-001~018,FILE-005,FILE-007~009,FILE-012,FILE-015와 OpenAPI 2.3.0에 재대조했다. - 검증: 전체 unit 72 files / 360 tests, server allowlist 36 tests, typecheck·lint·build가 통과했다.
- 신규 발견: Medium 1건. 기존
REV-P5-004~005는 수정 완료 상태를 유지한다.
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-P5-006 |
Medium | 수정 완료 | Series image crop 준비·연속 선택 상태가 저장 경계에 없다 | P5-R5 |
P5-R5 |
REV-P5-006 — Series image crop 준비·연속 선택 상태가 저장 경계에 없다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항:
FILE-005,FILE-007~009,FILE-012,FILE-015, PRD §10.5 - 소유 Task: 신규
P5-R5
관찰 내용
Series form은 유효 파일을 committed image로 바로 넣지는 않지만 createCropSource 준비 상태와 마지막 선택 token, reject 오류를 관리하지 않는다. submit은 dialog가 열린 뒤에는 막지만 Promise가 pending인 동안에는 가능하다. edit는 새 image 선택 의도가 반영되지 않은 update를 먼저 보낼 수 있고 이전 느린 선택이 최신 dialog를 덮을 수 있다.
근거
- 코드:
src/features/series/components/SeriesForm.tsx:80-116,129-151,198-206 - 문서:
prd.md:354,356-361,524-538 - 테스트 공백: crop 적용·취소는 검증하지만 pending source, reject, 선택 순서 역전은 고정하지 않는다.
재현 또는 검증 절차
- edit form에 pending
createCropSource를 주입하고 새 image를 선택한다. - Promise resolve 전에 저장해 새 image 없는 update가 진행되는지 확인한다.
- 느린 첫 파일과 빠른 둘째 파일을 선택한 뒤 첫 Promise를 마지막에 resolve해 stale dialog가 열리는지 확인한다.
영향
사용자가 교체를 선택했지만 기존 image가 유지되거나 잘못된 선택의 crop이 적용될 수 있다. 준비 실패도 화면에서 복구할 수 없다.
권장 수정 방향
Series form 내부에 selection token·준비 상태·inline 오류를 추가하고 준비/crop 중 submit을 비활성화·guard한다. crop 결과와 기존 서버 image 유지 계약은 그대로 둔다.
판정 기록:
- 2026-07-31 — 비동기 state와 파일 정책을 대조해 확정.
- 2026-07-31 —
P5-R5에서 Series image source 준비 상태와 selection token을 추가하고 준비/crop 중 submit을 차단했다. Promise reject는 inline 오류로 표시하고, crop 적용 결과만image에 commit하며 수정 취소는 기존 서버 image 유지 계약을 보존한다.
plan·goal 전환 및 종료 판정
REV-P5-006→plan-task.md신규P5-R5- 최종 결론: 신규 Medium 1건 수정 완료. Blocker/High 없음.
- 교차 Phase: 2026-07-30 visual QA에서 제거한 workspace
characterId는 Series 표시 문제가 아니라 PRD §7.2 위반이므로REV-P3-006/P3-R4가 복구를 소유한다.
11. 최종 Phase별 점검 — 2026-07-31
- 검토 범위: Series lookup·CRUD·image crop·연결/해제·전체 순서와 OpenAPI multipart/JSON payload를
P5에 재대조했다. - 실행 증거:
npm run test:run -- src/features/series8 files / 37 tests, mock Chromium Series 시나리오 7 tests가 통과했다. - 판정: Series 도메인 계약의 별도 신규 finding은 없다. 다만 create/update submit의 동기 재진입 guard와 저장 진행 표시 공백은 교차 품질 finding
REV-P9-006/P9-R6에 귀속했다. - 남은 위험: 실제 개발 API Series 수동 QA와
P9-R6완료가 필요하다. - 신규 Phase 5 Task: 없음. 중복 Task 대신
P9-R6에서 추적한다.
12. 종합 재점검 — 2026-07-31
- 검토 범위: Series genre·CRUD·210:297 image·contents 연결/해제·전체 순서 저장과 일반 mutation pending 상태를 재대조했다.
- 실행 증거: Series를 포함한 도메인 묶음 36 files / 206 tests, exact server E2E 18/18, 현재 두 project mock E2E 109 passed / 5 skipped가 통과했다.
- 판정:
P9-R6까지 포함한 Series 도메인 소유의 확정 신규 finding은 없다. - 교차 Phase: Series image frame·좌표와 Blob URL 수명은
REV-P1-016~017/P1-R10~R11에서 수정한다. - 남은 위험: 실제 개발 API Series mutation 수동 QA가 남아 있다. Safari/WebKit은 현재 지원 범위에서 제외한다.
- 신규 Phase 5 Task: 없음.
13. 요청 기준 재리뷰 — 2026-07-31
- 검토 범위: genre lookup, CRUD, 210:297 crop, contents 연결·해제·전체 순서, 표시 label과 mutation pending.
- 실행 증거: Series unit과 mock 4-project CRUD·responsive·axe·order journey가 0 failure였고 OpenAPI
keyword·genreId·contentIdList·ids계약 대조도 통과했다. - 판정: Phase 5 소유의 확정 신규 발견 사항 없음.
- 남은 위험: 실제 개발 API Series active-only·CRUD·연결·순서와 실기기 browser 수동 QA.
- 신규 Phase 5 Task: 없음.
14. 최종 재검증 — 2026-07-31
- 검토 범위: Series genre·CRUD, crop, contents 연결/해제, 전체 순서와 mutation pending/error 상태.
- 실행 증거: 전체 unit 394 tests와 4-project mock Series·responsive·axe·order 시나리오가 0 failure였고 OpenAPI request/response schema 대조도 통과했다.
- 판정: Phase 5 소유의 확정 신규 발견 사항 없음.
- 남은 위험: 실제 개발 API Series active-only·CRUD·연결·순서와 실기기 browser 수동 QA.
- 신규 Phase 5 Task: 없음.
15. 2026-07-31 문서 기준 재리뷰
- 검토 범위: Series genre lookup·CRUD·
210:297crop·contents 연결/해제·전체 순서·pending/error를SERIES-001~018·OpenAPI·P5·P10-T3에 대조했다. - 실행 증거: Series route를 포함한 실패 후보 5-spec focused는 25 tests passed, Series contract·form·order·contents test는 full output에서 통과했다. type·lint·build·server E2E 36 tests도 통과했다. full unit 비결정성은
REV-P9-009/P9-R9, crop frame은REV-P1-019/P1-R13으로 분리했다. - 판정: Series 도메인 계약·UI 소유의 확정 신규 발견 사항 없음. 후보·오탐·보류 0건, 신규 Phase 5 Task 없음.
- 남은 위험: 실제 Series active-only·CRUD·연결·순서 수동 QA와
P1-R13,P9-R9회귀가 남는다.