# 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 항목으로 유지한다.