# Phase 3 코드 리뷰·QA — 2026-07-29 ## 1. 리뷰 정보 | 항목 | 내용 | |---|---| | 리뷰 대상 | Phase 3 / Character workspace | | 기준 commit 또는 working tree | `dd30e36323543e8f60e9983326503653e8001f12`; 리뷰 시작 시 tracked diff 36개, 전체 변경 169개 | | 리뷰 일자 | 2026-07-29 | | 리뷰어 | Codex | | 기준 문서 | `prd.md`, `api-contract.openapi.json`, `plan-task.md` | | 리뷰 상태 | 판정 완료 | ## 2. 리뷰 목적과 범위 ### 목적 - `CHAR-001~018`, Character form/file 요구와 `P3-T1~P3-GATE`, `P10-T1` 결과를 대조한다. ### 포함 범위 - 코드: `src/features/characters`, Character route와 workspace 조합 - 테스트: Character unit/integration와 mock E2E - 문서: Character 요구사항, OpenAPI Character request/schema, Phase 3 체크 - 수동 검증: form control inventory와 payload 정적 대조 ### 제외 범위 - 공통 crop 계산은 Phase 1, cross-domain inactive policy와 field-error 접근성은 Phase 9에서 판정 ## 3. 판정 기준 심각도는 `Blocker/High/Medium/Low`, 상태는 `확정/오탐/보류/수정 완료`를 사용했다. ## 4. 검토한 근거 ### 문서와 코드 - 요구사항: `CHAR-003`, `CHAR-013`, `CHAR-018`, Character 생성·수정 폼, `FILE-002`, PRD `10.5` - 계약: `CharacterCreateRequest`, `CharacterUpdateRequest` - 코드: `CharacterCreatePage.tsx:37`, `CharacterCreatePage.tsx:52`, `CharacterCreatePage.tsx:75`, `CharacterCreatePage.tsx:88`, `CharacterEditPage.tsx:53`, `CharacterEditPage.tsx:68`, `CharacterEditPage.tsx:93`, `CharacterEditPage.tsx:106` - 테스트: `CharacterCreatePage.test.tsx`, `CharacterEditPage.test.tsx`, `character-api.test.ts` ### 실행 환경 ```text OS: macOS 26.0 Node: v24.12.0 npm: 11.7.0 API mode: unit injected client/MSW ``` ### 실행한 검증 | 명령 | 결과 | 핵심 증거 | |---|---|---| | `npm run test:run -- src/features/characters` | 성공 | 6 files / 23 tests passed | | OpenAPI property 출력 | 성공 | optional scalar 10개와 반복 배열 8개 확인 | | form control/payload 정적 대조 | 실패 재현 | 생성·수정 UI는 기본 4개 text/file + 원작 선택만 제공 | ## 5. 발견 사항 요약 | ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | |---|---|---|---|---|---| | `REV-P3-001` | High | 수정 완료 | Character form이 OpenAPI optional scalar·배열을 편집하지 못한다 | `P3-T3` | `P3-R1` | | `REV-P3-002` | Medium | 수정 완료 | create/update 실패 시 form이 영구 제출 중 상태가 된다 | `P3-T3` | `P3-R2` | | `REV-P3-003` | Medium | 수정 완료 | Character image가 확장자와 MIME 조합을 검증하지 않는다 | `P3-T3` | `P3-R2` | ## 6. 발견 사항 상세 ### REV-P3-001 — Character form의 확정 입력 범위가 누락됐다 - **심각도:** High - **상태:** 수정 완료 - **관련 요구사항:** Character 생성·수정 폼, `CHAR-003`, `CHAR-018` - **관련 계약:** `CharacterCreateRequest`, `CharacterUpdateRequest` - **소유 Task:** 신규 `P3-R1` **관찰 내용** 생성·수정 화면은 `name`, `systemPrompt`, `description`, `originalWorkId`, image와 수정의 읽기 전용 region만 제공한다. PRD가 명시한 `age`, `gender`, `mbti`, `speechPattern`, `speechStyle`, `appearance`, `region`, `originalTitle`, `originalLink`, `characterType` 및 8개 반복 배열 입력이 없다. **재현 또는 검증 절차** 1. Character 생성 또는 수정 route를 연다. 2. OpenAPI request property와 visible label을 대조한다. 3. 해당 18개 optional 입력을 찾거나 수정할 수 없다. 4. API helper의 type/test는 이 필드를 수용하지만 UI test는 입력 경로를 검증하지 않는다. **영향** 운영자가 계약에 포함된 캐릭터 프로필·관계·기억 데이터를 생성·수정할 수 없고 `P3-T3` 완료 판정과 불일치한다. **권장 조치** 현재 request type을 재사용해 scalar와 반복 section을 최소 form component로 추가하고 create/update serializer·dirty state·validation E2E를 보강한다. **판정 기록** - 2026-07-29 — PRD·OpenAPI property와 실제 form control을 대조해 확정. - 2026-07-30 — `P3-R1`에서 create/edit optional scalar·반복 배열 editor와 canonical payload test를 추가해 수정 완료로 판정. ### REV-P3-002 — Character mutation 실패 후 재시도할 수 없다 - **심각도:** Medium - **상태:** 수정 완료 - **관련 요구사항:** PRD `10.5` 오류·저장 상태 - **관련 계약:** Character mutation 공통 오류 envelope - **소유 Task:** 신규 `P3-R2` **관찰 내용** `setIsSubmitting(true)` 뒤 `await createCharacter/updateCharacter`를 catch/finally 없이 실행한다. 요청이 reject되면 서버 오류가 표시되지 않고 제출 버튼이 계속 비활성화된다. **영향** 일시적 네트워크·server 오류 뒤 입력을 유지한 재시도가 불가능하다. **권장 조치** server/form 오류 상태와 `finally` 복구를 추가하고 create/update rejection focused test를 먼저 작성한다. **판정 기록** - 2026-07-29 — 두 submit 경로의 예외 처리를 대조해 확정. - 2026-07-30 — `P3-R2`에서 create/update rejection retry test를 RED로 추가한 뒤 `try/catch/finally`와 form-level 오류를 적용해 수정 완료로 판정. 검증: `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, `npm run test:run -- src/features/characters` 6 files / 32 tests passed, `npm run e2e:mock -- tests/e2e/character-workspace.spec.ts` 병렬 실행은 axe timeout 4건으로 실패, focused Chromium 1 passed, 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. ### REV-P3-003 — Character image의 format pair 검증이 없다 - **심각도:** Medium - **상태:** 수정 완료 - **관련 요구사항:** `FILE-002` - **관련 계약:** JPEG/PNG image - **소유 Task:** 신규 `P3-R2` **관찰 내용** `validateImage`는 MIME과 크기만 검사한다. 예를 들어 `profile.gif` 또는 `profile.txt`라는 이름에 `image/png` MIME을 부여하면 통과한다. **영향** 허용 format과 다른 multipart가 client에서 전송되고 backend 거부가 불필요하게 늦게 발생한다. **권장 조치** Community에서 사용하는 extension↔MIME pair 방식의 Character 전용 policy를 추가하고 mismatch test를 작성한다. **판정 기록** - 2026-07-29 — create/edit validator의 filename 사용 0건으로 확정. - 2026-07-30 — Character image policy가 `.jpg/.jpeg ↔ image/jpeg`, `.png ↔ image/png` 조합을 검증하고, create/edit mismatch test가 crop source 0회와 mutation request 0회를 확인해 수정 완료로 판정. 검증 명령과 결과는 `REV-P3-002`의 2026-07-30 기록과 동일하다. ## 7. 확정 항목의 plan·goal 전환 - `REV-P3-001` → `P3-R1` - `REV-P3-002~003` → `P3-R2` ## 8. 리뷰 종료 판정 | 판정 항목 | 결과 | 근거 | |---|---|---| | 리뷰 범위 전체 확인 | 충족 | Character code/test/contract 대조 | | 후보 항목 판정 완료 | 충족 | 3건 확정 | | 확정 항목 plan 반영 | 충족 | `P3-R1`, `P3-R2` | | 보류 항목 담당·재개 조건 | 해당 없음 | 보류 없음 | | 검증 명령과 결과 기록 | 충족 | §4 | **최종 결론:** 수정 goal 필요. **남은 항목:** `P3-R2`, Phase 9의 inactive·접근성 회귀. ## 9. 수정 후 검증 기록 - 2026-07-30 `REV-P3-001 / P3-R1` 수정 완료 확인 - RED: `npm run test:run -- src/features/characters/tests/CharacterCreatePage.test.tsx src/features/characters/tests/CharacterEditPage.test.tsx`에서 신규 3개 test가 optional field label 누락으로 실패했다. - GREEN: `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. - E2E: `npm run e2e:mock -- tests/e2e/character-workspace.spec.ts` 결과 41 passed / 3 skipped. optional 입력 추가로 keyboard-only helper의 tab 한도만 80회로 조정했다. - 품질 게이트: `npm run typecheck`, `npm run lint`, `npm run build` 모두 exit 0. - 2026-07-30 `REV-P3-001 / P3-R1` 독립 리뷰 보완 - 발견: 수정 상세 응답에 없는 `originalTitle`/`originalLink`가 일반 저장 시 `null`로 전송되어 기존 서버 값을 지울 수 있는 blocker를 확인했다. - RED: `npm run test:run -- src/features/characters/tests/CharacterEditPage.test.tsx` 결과 2 failed로 이름 수정과 원작 선택 해제 저장에서 `originalTitle: null`, `originalLink: null` 전송을 재현했다. - 수정: 두 필드에 touched flag를 추가해 edit 기본 저장은 omit하고, 사용자가 입력 후 삭제한 경우에만 `null`을 전송하게 했다. - 재검증: `npm run test:run -- src/features/characters/tests/CharacterEditPage.test.tsx` 결과 1 file / 7 tests passed. `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.