Files
voiceon-character-admin/docs/20260725_AI캐릭터관리자웹/reviews/review-phase-1-20260729.md

190 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 1 코드 리뷰·QA — 2026-07-29
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 1 / 공통 API·인증·file/crop 기반 |
| 기준 commit 또는 working tree | `dd30e36323543e8f60e9983326503653e8001f12`; 리뷰 시작 시 tracked diff 36개, 전체 변경 169개 |
| 리뷰 일자 | 2026-07-29 |
| 리뷰어 | Codex |
| 기준 문서 | `prd.md`, `api-contract.openapi.json`, `plan-task.md` |
| 리뷰 상태 | 판정 완료 |
## 2. 리뷰 목적과 범위
### 목적
- `AUTH-001~013`, `FILE-001~015`, 공통 API envelope와 crop 계약을 현재 구현과 대조한다.
- 기존 `review-phase-0-1.md`에서 수정 완료된 항목은 보존하고 새 회귀만 판정한다.
### 포함 범위
- 코드: `src/shared/api`, `src/shared/lib/crop-image.ts`, `src/shared/ui/image-crop-dialog.tsx`, 인증 저장소
- 테스트: 공통 API 인증·오류, crop 계산·Dialog
- 문서: Phase 1 Task·Gate와 PRD 인증/file 기준
- 수동 검증: 코드 계산과 keyboard/pointer control 정적 대조
### 제외 범위
- resource별 form과 multipart serializer, 실제 모바일 pinch 기기 QA
## 3. 판정 기준
| 심각도 | 기준 |
|---|---|
| Blocker | 보안·데이터 손실 또는 핵심 흐름 불능 |
| High | 확정 요구사항·계약 위반 또는 주요 회귀 |
| Medium | 제한 조건의 기능·접근성·복구 문제 |
| Low | 비핵심 UX·문서 정합성 |
상태는 `확정/오탐/보류/수정 완료`를 사용했다.
## 4. 검토한 근거
### 문서와 코드
- 요구사항: `AUTH-005`, `FILE-012`, PRD `10.4`
- 코드: `src/shared/api/client.ts:92`, `src/shared/api/client.ts:102`, `src/shared/api/client.ts:122`, `src/shared/lib/crop-image.ts:67`, `src/shared/lib/crop-image.ts:69`, `src/shared/ui/image-crop-dialog.tsx:38`, `src/shared/ui/image-crop-dialog.tsx:154`
- 테스트: `src/shared/api/__tests__/client-auth.test.ts`, `src/shared/lib/crop-image.test.ts`, `src/shared/ui/__tests__/image-crop-dialog.test.tsx`
### 실행 환경
```text
OS: macOS 26.0
Node: v24.12.0
npm: 11.7.0
API mode: unit stub/MSW
```
### 실행한 검증
| 명령 또는 검증 | 결과 | 핵심 증거 |
|---|---|---|
| `npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx src/shared/api/__tests__/client-auth.test.ts src/shared/api/__tests__/client.test.ts` | 성공 | 4 files / 27 tests passed |
| `node` crop 계산 | 실패 재현 | 600px 원본, zoom 1.5에서 source crop 400px, output 600px, 1.5배 확대 |
| 코드 경로 대조 | 실패 재현 | 401 처리가 정상 오류 envelope parse 뒤에만 존재 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P1-011` | High | 수정 완료 | 비정상 envelope·JSON인 401은 session을 제거하지 않는다 | `P1-T2` | `P1-R6` |
| `REV-P1-012` | High | 수정 완료 | zoom crop이 선택 영역보다 큰 결과를 만들어 no-upscale을 위반한다 | `P1-T5` | `P1-R7` |
| `REV-P1-013` | Medium | 수정 완료 | crop Dialog에 pinch/zoom 입력이 없다 | `P1-T5` | `P1-R7` |
## 6. 발견 사항 상세
### REV-P1-011 — 비정상 401에서 인증 만료 처리가 누락된다
- **심각도:** High
- **상태:** 수정 완료
- **관련 요구사항:** `AUTH-005`
- **관련 계약:** 보호 요청의 HTTP 401
- **소유 Task:** 신규 `P1-R6`
**관찰 내용**
`response.json()` 실패와 envelope schema 실패는 즉시 공통 `ApiError`를 던진다. session clear와 login 이동은 정상 오류 envelope가 parse된 뒤의 `!apiResponse.success` 분기에서만 실행된다.
**재현 또는 검증 절차**
1. 인증이 필요한 요청에 session을 저장한다.
2. server가 body 없는 401, non-JSON 401 또는 malformed envelope 401을 반환하게 한다.
3. 현재 결과는 status 401 오류만 발생하고 session clear/redirect callback은 0회다.
4. 요구 결과는 body 형태와 무관하게 보호 요청 401에서 1회 session clear와 login 이동이다.
**영향**
만료·무효 token이 브라우저 session에 남고 사용자가 보호 화면에서 반복 실패할 수 있다.
**권장 조치**
응답 body parse 전에 HTTP status 기반 인증 만료를 공통 처리하되 동시 401 burst 1회 규칙을 유지하고 malformed/empty 401 회귀 test를 추가한다.
**판정 기록**
- 2026-07-29 — 코드의 모든 401 경로를 대조해 확정.
- 2026-07-30 — `P1-R6`에서 HTTP status 기반 인증 만료 처리를 body parse 전에 수행하도록 수정하고 malformed JSON, empty body, schema mismatch 401 회귀를 추가해 수정 완료로 판정.
### REV-P1-012 — zoom crop 결과가 선택 원본 영역을 확대한다
- **심각도:** High
- **상태:** 수정 완료
- **관련 요구사항:** `FILE-012`
- **관련 계약:** resource별 `noUpscale=true`
- **소유 Task:** 신규 `P1-R7`
**관찰 내용**
출력 크기는 항상 `zoom=1`인 crop 영역으로 계산하지만 실제 source 영역은 현재 zoom으로 축소된다. 600×600 원본을 1.5배 확대하면 source는 400×400인데 output은 600×600이다.
**재현 또는 검증 절차**
1. 600×600 이미지와 1:1, maxWidth 800, noUpscale 정책을 연다.
2. zoom을 1.5로 바꾸고 적용한다.
3. `calculateCropSourceRect`는 400×400, Dialog는 output 600×600을 전달한다.
4. 요구 결과는 output이 선택 source crop의 pixel 크기를 넘지 않는 것이다.
**영향**
Character·Audio·Series·Community JPEG/PNG crop이 확대되어 품질이 저하되고 파일 계약을 위반한다.
**권장 조치**
output size 계산에 현재 zoom의 source rect를 사용하고 zoom 경계별 실제 canvas draw test를 추가한다.
**판정 기록**
- 2026-07-29 — 계산 결과 `scale=1.5`로 확정.
- 2026-07-30 — `P1-R7`에서 `CropOutputSizeRequest.zoom`을 반영해 zoom 1.5의 600×600 source crop output을 400×400으로 제한했고, focused/broad Vitest와 typecheck로 수정 완료 판정.
### REV-P1-013 — crop Dialog가 pinch/zoom을 지원하지 않는다
- **심각도:** Medium
- **상태:** 수정 완료
- **관련 요구사항:** PRD `10.4`
- **관련 계약:** 없음
- **소유 Task:** 신규 `P1-R7`
**관찰 내용**
range, 버튼/keyboard zoom과 단일 pointer drag는 있으나 두 pointer 거리 또는 gesture를 처리하는 pinch 입력 경로가 없다.
**영향**
tablet/mobile touch 사용자가 명시된 pinch/zoom 방식으로 crop을 조작할 수 없다.
**권장 조치**
기존 range/keyboard 대안을 유지한 채 두 pointer pinch를 최소 구현하고 pointer 회귀 test와 실제 touch QA 항목을 추가한다.
**판정 기록**
- 2026-07-29 — pointer handler와 테스트 전체 검색에서 pinch 경로 0건으로 확정.
- 2026-07-30 — `P1-R7`에서 native pointer map/ref 기반 two-pointer pinch zoom을 추가하고 pointerup/pointercancel/pointerleave cleanup을 연결했다. Dialog 회귀 test로 pinch zoom 1.5, 기존 drag/range/keyboard 경로를 확인해 수정 완료 판정.
## 7. 확정 항목의 plan·goal 전환
- `REV-P1-011``P1-R6`
- `REV-P1-012~013``P1-R7`
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | API 401·crop code/test 대조 |
| 후보 항목 판정 완료 | 충족 | 3건 모두 확정 |
| 확정 항목 plan 반영 | 충족 | `P1-R6`, `P1-R7` |
| 보류 항목 담당·재개 조건 | 해당 없음 | 보류 없음 |
| 검증 명령과 결과 기록 | 충족 | §4 |
**최종 결론:** 확정 항목 수정 완료.
**남은 항목:** 없음.
## 9. 수정 후 검증 기록
- 2026-07-30 `P1-R6` — 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건 실패했다. GREEN 같은 command는 2 files / 21 tests passed. 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 `npm run test:run -- src/features/auth src/app``REV-P10-001`/`P10-R1`의 stale fallback assertion 6건으로 실패해 기존 Phase 10 후속 범위로 유지한다.
- 2026-07-30 `P1-R7` — RED `npm run test:run -- src/shared/lib/crop-image.test.ts src/shared/ui/__tests__/image-crop-dialog.test.tsx`에서 zoom 1.5 output 600×600과 pinch 미지원으로 2 failed / 9 passed를 확인했다. 1차 review blocker 보강 RED는 210:297 zoom 1.8 output 236×334의 source height 1px 초과와 `touch-action: none` 누락으로 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 blocker 보강 RED는 aspect 2 tiny source 1×0 붕괴로 `npm run test:run -- src/shared/lib/crop-image.test.ts` 1 failed / 6 passed였다. 3차 review blocker 보강 RED는 free aspect tiny source 1×2 output이 source height를 초과해 `npm run test:run -- src/shared/lib/crop-image.test.ts` 2 failed / 5 passed였다. GREEN focused는 2 files / 14 tests passed로 zoom 1.5 output 400×400, 210:297 zoom 1.8 output 236×333, tiny source 1×1, aspect 2 tiny source 1×1, free aspect tiny source 1×1, two-pointer pinch zoom 1.5와 preview `touch-action: none`을 확인했다. Broad crop regression은 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 경로를 확인했다. `npm run typecheck`, `npm run lint`는 exit 0이고, `src/shared/ui`, `tests/e2e` LSP diagnostics는 오류 0건이었다. 실제 tablet touch 장비 QA는 수행하지 못해 릴리스 QA 항목으로 유지한다.