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

165 lines
8.2 KiB
Markdown

# Phase 9 코드 리뷰·QA — 2026-07-29
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 9 / 교차 회귀·반응형·접근성·보안 |
| 기준 commit 또는 working tree | `dd30e36323543e8f60e9983326503653e8001f12`; 리뷰 시작 시 tracked diff 36개, 전체 변경 169개 |
| 리뷰 일자 | 2026-07-29 |
| 리뷰어 | Codex |
| 기준 문서 | `prd.md`, `api-contract.openapi.json`, `plan-task.md` |
| 리뷰 상태 | 수정 완료 |
## 2. 리뷰 목적과 범위
### 목적
- 도메인별 focused test가 놓친 read-only policy, form 오류 접근성과 통합 Gate 신뢰도를 교차 점검한다.
### 포함 범위
- 코드: Character/Audio/Series/Community/Comments mutation entrypoint, form error markup
- 테스트: 7개 domain mock E2E, 전체 unit, typecheck/lint/build
- 문서: PRD `7.2`, `10.5~10.7`, `14`, `P9-T1~P9-GATE`
- 수동 검증: inactive Character 응답에 대한 화면별 action inventory
### 제외 범위
- 실제 Edge/실기기 최신 2개 major와 실제 개발 API fixture
## 3. 판정 기준
심각도는 `Blocker/High/Medium/Low`, 상태는 `확정/오탐/보류/수정 완료`를 사용했다.
## 4. 검토한 근거
### 문서와 코드
- 요구사항: `CHAR-007`, PRD `7.2`, `10.5`, `14.1`
- 코드: `CharacterEditPage.tsx:207`, `AudioContentListPage.tsx:96`, `AudioContentDetailPage.tsx:63`, `AudioContentDetailPage.tsx:69`, `AudioContentFormPage.tsx:77`, `SeriesListPage.tsx:89`, `SeriesDetailPage.tsx:63`, `SeriesFormPage.tsx:44`, `SeriesOrderPage.tsx:49`, `CommunityPostSheet.tsx:169`
- form markup: Character/Audio/Series/Community inputs의 `aria-invalid`와 별도 `role=alert`
- 테스트: `tests/e2e/resource-workflows.spec.ts`, `responsive-capabilities.spec.ts`, `accessibility.spec.ts`, domain E2E
### 실행 환경
```text
OS: macOS 26.0
Node: v24.12.0
npm: 11.7.0
Browser: Playwright Chromium/WebKit/mobile viewport projects
API mode: VITE_API_MODE=mock
```
### 실행한 검증
| 명령 또는 검증 | 결과 | 핵심 증거 |
|---|---|---|
| `npm run test:run` | 실패 | 66 files / 290 tests, 6 failed·284 passed; `REV-P10-001` |
| `npm run e2e:mock --` 7개 Phase 10 spec | 실패 | 168 tests: 147 passed, 17 skipped, WebKit timeout 4건 |
| WebKit 실패 spec 단일 worker 재실행 | 성공 | 4 passed / 28.4초; 병렬 timeout은 단독 재현되지 않음 |
| `npm run typecheck` | 성공 | exit 0 |
| `npm run lint` | 성공 | exit 0 |
| `npm run build` | 성공 | 249 modules transformed, production build exit 0 |
| inactive action code inventory | 실패 재현 | banner와 무관하게 여러 직접 mutation route/action 활성 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P9-001` | High | 수정 완료 | 비활성 Character에서 여러 하위 mutation이 계속 가능하다 | `P9-T1` | `P9-R1` |
| `REV-P9-002` | Medium | 수정 완료 | form field 오류가 입력과 연결되지 않고 첫 오류 focus가 없다 | `P9-T2` | `P9-R2` |
## 6. 발견 사항 상세
### REV-P9-001 — inactive workspace의 쓰기 차단이 도메인별로 일관되지 않다
- **심각도:** High
- **상태:** 수정 완료
- **관련 요구사항:** `CHAR-007`, PRD `7.2`, `14.1`
- **관련 계약:** Character detail `isActive=false`
- **소유 Task:** 신규 `P9-R1`
**관찰 내용**
workspace는 read-only banner만 표시한다. Character edit 직접 route, Audio create/edit/deactivate와 댓글, Series create/edit/link/unlink/order, Community Sheet의 CommentThread는 `character.isActive`를 mutation capability에 결합하지 않는다. Community post mutation과 FanTalk reply entrypoint만 일부 차단한다.
**재현 또는 검증 절차**
1. Character detail 응답을 `isActive=false`로 반환한다.
2. `/ai-characters/:id/edit`, Audio create/edit/detail, Series create/edit/detail/order, Community post Sheet로 진입한다.
3. mutation form/button 또는 comment 작성·삭제가 노출되고 client 요청을 실행할 수 있다.
4. 요구 결과는 조회만 유지하고 모든 하위 mutation 진입점과 직접 route가 차단되는 것이다.
**영향**
frontend read-only 안전장치가 무력화되어 비활성 Character 데이터에 쓰기 요청이 발생한다. backend 검증이 있어도 반복 오류와 운영 오조작을 유발한다.
**권장 조치**
viewport capability와 별도로 `character.isActive`를 결합한 명시적 mutation capability를 각 Page에서 계산하고 form/direct route까지 차단한다. Audio/Series/Comments/Character direct-route E2E를 추가한다.
**판정 기록**
- 2026-07-29 — 모든 workspace consumer의 `isActive` 사용처를 대조해 확정.
- 2026-07-30 — `P9-R1`에서 inactive workspace mutation 차단을 구현하고 focused unit 28 files / 152 tests, 관련 mock E2E 103 passed / 13 skipped, typecheck/lint/build exit 0으로 수정 완료.
### REV-P9-002 — field error 접근성 계약이 구현되지 않았다
- **심각도:** Medium
- **상태:** 수정 완료
- **관련 요구사항:** PRD `10.5`, `10.7`
- **관련 계약:** 없음
- **소유 Task:** 신규 `P9-R2`
**관찰 내용**
주요 form은 `aria-invalid`와 별도 `role=alert` 문구는 제공하지만 input의 `aria-describedby`/`aria-errormessage`와 error element ID 연결이 없다. submit validation 뒤 첫 오류 control로 focus를 옮기는 코드도 없다.
**영향**
screen reader와 keyboard 사용자가 어떤 입력에 어떤 오류가 생겼는지, 어디서 수정해야 하는지 빠르게 파악하기 어렵다.
**권장 조치**
form별 stable error ID와 describedby 연결, 제출 후 DOM 순서 첫 invalid focus helper를 최소 공통 규칙으로 적용하고 keyboard/accessible-description test를 추가한다.
**판정 기록**
- 2026-07-29 — Character/Audio/Series/Community form markup과 focus 호출을 검색해 확정.
- 2026-07-30 — `P9-R2`에서 주요 form error ID·description 연결과 첫 invalid focus를 구현하고 focused unit 5 files / 35 tests, `src/features` 35 files / 195 tests, accessibility mock E2E 9 passed / 3 skipped, typecheck/lint/build exit 0으로 수정 완료.
## 7. 확정 항목의 plan·goal 전환
- `REV-P9-001``P9-R1`
- `REV-P9-002``P9-R2`
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | 교차 policy·form·Gate 대조 |
| 후보 항목 판정 완료 | 충족 | 2건 수정 완료 |
| 확정 항목 plan 반영 | 충족 | `P9-R1`, `P9-R2` |
| 보류 항목 담당·재개 조건 | 충족 | 실제 browser/server QA는 릴리스 QA와 `P10-GATE` |
| 검증 명령과 결과 기록 | 충족 | §4 |
**최종 결론:** 수정 검증 완료.
**남은 항목:** 실제 개발 API fixture가 필요한 server integration은 `P10-GATE` 수동 QA로 분리한다.
## 9. 수정 후 검증 기록
애플리케이션 수정은 아직 하지 않았다. 병렬 통합 실행에서 timeout 난
WebKit 4건을 `--project=webkit --workers=1 --last-failed`와 단일 worker로
다시 실행해 4 passed / 28.4초를 확인했다. 병렬 부하에서의 Gate
불안정성은 남지만 동일 시나리오가 단독 재현되지 않아 별도 앱 결함으로
확정하지 않았다. `npm run build`는 249 modules transformed와 exit 0으로
통과했다.
### P9-R1~P9-R2 수정 후 검증 — 2026-07-30
- `P9-R1`: inactive Character workspace의 하위 mutation 진입점과 직접 route를 read-only guidance로 교체했다. `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는 103 passed / 13 skipped였다.
- `P9-R2`: 주요 form 오류 문구를 stable ID와 `aria-describedby`/`aria-invalid`로 연결하고 submit 뒤 첫 invalid control focus를 적용했다. focused unit은 5 files / 35 tests passed, `npm run test:run -- src/features`는 35 files / 195 tests passed, `npm run e2e:mock -- tests/e2e/accessibility.spec.ts`는 9 passed / 3 skipped였다.
- 공통 gate: `npm run typecheck`, `npm run lint`, `npm run build`는 모두 exit 0이었다. 이후 `P9-GATE` fresh 재검증은 `npm run test:run` 72 files / 354 tests passed, exact `npm run e2e:mock` 206 passed / 22 skipped, `npm run e2e` server allowlist 36 passed로 통과했다.