20 KiB
Phase 3 Character Workspace 코드 리뷰·QA
1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 3 / Character 목록·상세·편집·워크스페이스 |
| 기준 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. 리뷰 목적과 범위
목적
- Character CRUD, URL 문맥, 비활성 read-only 정책과 탭 접근성이 요구사항·계약에 맞는지 확인한다.
- 데스크톱·모바일·keyboard-only 워크스페이스 회귀 범위를 점검한다.
포함 범위
- 코드:
src/features/characters,src/layouts/CharacterWorkspaceLayout.tsx - 테스트: Character unit/integration 및
character-workspace.spec.ts - 문서: Character 요구사항, PRD §7.2·§14.2,
P3-* - 수동 검증: WAI-ARIA Tabs Pattern과 markup·keyboard handler 정적 대조
제외 범위
- Phase 4~8 하위 리소스의 도메인별 mutation
- 실제 외부 서버 Character fixture를 이용한 수동 QA
3. 판정 기준
| 심각도 | 기준 |
|---|---|
| Blocker | 핵심 Character 관리 불능 또는 데이터·보안 위험 |
| High | Character 요구사항·계약의 주요 위반 |
| Medium | 특정 입력 방식·화면에서 기능 또는 접근성이 깨짐 |
| Low | 비핵심 UX·운영 문구·유지보수 정합성 문제 |
상태는 후보, 확정, 오탐, 보류, 수정 완료를 사용한다.
4. 검토한 근거
문서와 코드
- 요구사항: Character 요구사항, PRD §7.2, §10.3, §14.2
- 계획:
P3-T1~P3-GATE - 코드:
src/layouts/CharacterWorkspaceLayout.tsx:50-63 - 테스트:
src/layouts/CharacterWorkspaceLayout.test.tsx:89-105,tests/e2e/character-workspace.spec.ts - 외부 기준: WAI-ARIA Authoring Practices Guide Tabs Pattern
실행 환경
macOS 26.0 / Node v24.12.0 / npm 11.7.0
Playwright 4 projects, 320/768/1280px와 200% zoom 자동화 포함
실행한 검증
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|---|---|---|
npm run test:run |
성공 | 전체 72 files, 354 tests passed |
npm run e2e:mock |
부분 실패 | Character workspace 관련 실행 항목은 통과 또는 프로젝트 정책상 skip; 전체 실패는 Phase 0 origin 4건과 비재현 Series timeout |
npm run typecheck / npm run lint / npm run build |
성공 | 모두 exit 0 |
| tab markup 정적 검증 | 실패 | 5개 role=tab이 모두 기본 tab stop이며 ArrowLeft/ArrowRight·roving tabindex 없음 |
| 비활성 안내 문구 정적 검증 | 실패 | 사용자 화면과 test에 내부 Task ID P3-T2 노출 |
5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-P3-004 |
Medium | 수정 완료 | ARIA tablist가 화살표 키·roving tabindex·tabpanel 이름 연결을 제공하지 않는다 | P3-R3 |
P3-R3 |
REV-P3-005 |
Low | 수정 완료 | 비활성 캐릭터 안내에 내부 계획 ID와 오래된 범위 문구가 노출된다 | P3-R3 |
P3-R3 |
6. 발견 사항 상세
REV-P3-004 — ARIA tablist가 화살표 키·roving tabindex·tabpanel 이름 연결을 제공하지 않는다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항: PRD §7.2, §10.3, §14.2
- 관련 계약: 없음
- 소유 Task: 신규
P3-R3
관찰 내용
워크스페이스는 링크 5개를 role="tab"으로 선언하지만 모두 기본 tab stop이다. 선택 tab만 tabIndex=0으로 두는 roving tabindex, 좌우 화살표 이동, Home/End 처리와 tabpanel의 aria-labelledby 연결이 없다.
근거
- 코드:
src/layouts/CharacterWorkspaceLayout.tsx:56-63 - 테스트: tabs의 선택 상태는 확인하지만 화살표 이동·roving tabindex·tabpanel accessible name 회귀 test가 없음
- 문서: PRD §14.2는 keyboard-only 탭 이동을 성공 기준으로 둠
- 표준: WAI-ARIA Tabs Pattern은 horizontal tablist에서 좌우 화살표 이동과 활성 tab 하나의 tab sequence 진입을 정의함
재현 또는 검증 절차
/ai-characters/:characterId에서 키보드로 워크스페이스 tablist에 진입한다.ArrowRight또는ArrowLeft를 누른다.- 선택·focus가 다음/이전 tab으로 이동하지 않는 것을 확인한다.
Tab을 반복하면 모든 tab을 개별 순회하며 tabpanel이 tab label로 명명되지 않는 것을 확인한다.
영향
키보드와 보조기술 사용자는 ARIA tab widget의 표준 동작을 사용할 수 없고, five-tab navigation에 불필요한 tab stop을 반복한다.
권장 조치
기존 link navigation을 유지하면서 roving tabindex, 방향키/Home/End, tab ID와 aria-labelledby 연결을 추가하고 component/E2E keyboard 회귀 test를 작성한다.
판정 기록
- 2026-07-30 — PRD keyboard 기준, APG pattern과 현재 markup·handler를 대조해 확정.
- 2026-07-30 —
P3-R3에서 link navigation 의미를nav로 정정하고 현재 위치·keyboard focus 회귀 test를 추가해 수정 완료 판정.
REV-P3-005 — 비활성 캐릭터 안내에 내부 계획 ID와 오래된 범위 문구가 노출된다
- 심각도: Low
- 상태: 수정 완료
- 관련 요구사항: 비활성 Character workspace read-only 정책
- 관련 계약: 없음
- 소유 Task: 신규
P3-R3
관찰 내용
비활성 banner가 “P3-T2 범위에는 ... 진입점이 없습니다”라고 표시한다. 현재는 후속 Phase에서 mutation이 구현됐고 공통 정책으로 차단되므로 내부 Task ID와 과거 범위 설명 모두 사용자 관점에서 부정확하다.
근거
- 코드:
src/layouts/CharacterWorkspaceLayout.tsx:50-54 - 테스트:
src/layouts/CharacterWorkspaceLayout.test.tsx:103이 내부 문구를 고정 - 문서: PRD §12는 비활성 workspace의 mutation 진입점 차단이라는 제품 정책만 규정
재현 또는 검증 절차
- 비활성 Character 상세에 진입한다.
- read-only banner를 읽는다.
- 사용자에게 의미 없는 계획 ID
P3-T2와 현재 구현 범위에 맞지 않는 문구가 노출되는 것을 확인한다.
영향
운영자에게 내부 개발 정보를 노출하고 실제 read-only 정책의 이유와 범위를 혼동시킨다.
권장 조치
Task ID를 제거하고 “비활성 캐릭터에서는 생성·수정·삭제할 수 없다”처럼 현재 정책을 설명하는 운영 문구로 교체한다.
판정 기록
- 2026-07-30 — 현재 후속 Phase 구현과 고정 test 문구를 대조해 확정.
- 2026-07-30 —
P3-R3에서 내부 Task ID를 제거하고 현재 read-only 정책 문구로 교체해 수정 완료 판정.
7. 확정 항목의 plan·goal 전환
REV-P3-004,REV-P3-005→plan-task.md신규P3-R3- goal objective:
[P3-R3] 워크스페이스 tab keyboard semantics와 비활성 운영 문구를 복구한다.
8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | Character 코드·test·접근성 기준 대조 |
| 후보 항목 판정 완료 | 충족 | 2건 수정 완료 |
| 확정 항목 plan 반영 | 충족 | P3-R3 완료 |
| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 없음 |
| 검증 명령과 결과 기록 | 충족 | §4 |
최종 결론: REV-P3-004, REV-P3-005 수정 완료
남은 항목: Phase 3 자체 회귀 없음. 공통 비활성화 mutation pending·오류 복구는 REV-P9-004/P9-R4에서 추적한다.
9. 수정 후 검증 기록
P3-R3 수정 후 검증 — 2026-07-30
- RED:
npm run test:run -- src/layouts/CharacterWorkspaceLayout.test.tsx는 기존 tab semantics와 내부 Task 문구 assertion에서 2 failed / 3 passed였다. - GREEN: link 기반 화면 이동은 native
navsemantics와aria-current=page로 정렬하고, 비활성 안내에서P3-T2를 제거했다. focused layout test는 5 passed였다. - 회귀: 관련 9 files / 45 tests, Chromium Character E2E 11 tests가 통과했고
npm run typecheck,npm run lint는 exit 0이었다. - 2차 전체 검증:
npm run test:run72 files / 358 tests,npm run build255 modules transformed, server allowlist E2E 36 passed로 현재 수정 상태를 재확인했다.
10. 2026-07-31 재점검
실행·판정 요약
- 기준: commit
dd30e36323543e8f60e9983326503653e8001f12, PRD §7.1~7.2·CHAR-001~018·FILE-010과 OpenAPI Character operation 재대조. - 검증: 전체 unit 72 files / 360 tests, server allowlist 36 tests, typecheck·lint·build가 통과했다.
- 신규 발견: High 1건, Medium 2건. 기존
REV-P3-004~005는 수정 완료 상태를 유지한다.
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
REV-P3-006 |
Medium | 수정 완료 | 워크스페이스 header가 필수 characterId를 숨긴다 |
P3-R4 |
P3-R4 |
REV-P3-007 |
Medium | 수정 완료 | Character URL이 계약 query searchTerm 대신 search를 사용한다 |
P3-R5 |
P3-R5 |
REV-P3-008 |
High | 수정 완료 | Character 원본 image를 crop 적용 전에 제출할 수 있다 | P3-R6 |
P3-R6 |
REV-P3-006 — 워크스페이스 header가 필수 characterId를 숨긴다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항: PRD §7.2
- 소유 Task: 신규
P3-R4
관찰 내용
수정 전 공통 workspace header는 이미지·이름·상태만 표시하며 character.id를 렌더링하지 않았다. 테스트는 요구사항과 반대로 characterId: 미노출을 성공 조건으로 고정했다. 프로필 본문의 characterUUID는 numeric characterId를 대체하지 않는다.
근거
- 문서:
prd.md:155-159— 상단에characterId를 항상 표시 - 코드:
src/layouts/CharacterWorkspaceLayout.tsx:40-55 - 테스트:
src/layouts/CharacterWorkspaceLayout.test.tsx:81-85 - 이력:
plan-task.md의 2026-07-30 visual QA 기록이 header ID 제거를 명시한다.
재현 또는 검증 절차
/ai-characters/101또는 하위 workspace route에 진입한다.- 공통 header에서 이미지·루나·공개 상태는 보이지만 numeric
101식별자는 없음을 확인한다.
영향 및 권장 수정 방향
이름이 같거나 유사한 캐릭터를 운영할 때 현재 작업 대상을 오인할 수 있다. 공통 header에 label과 함께 numeric ID를 복구하고 active/inactive 및 모든 하위 route 회귀 test를 추가한다.
판정 기록:
- 2026-07-31 — PRD 명시 요구사항과 구현·반대 assertion을 대조해 확정.
- 2026-07-31 —
P3-R4에서 active/inactive header에 numericcharacterId를 복구하고 layout·Character focused unit으로 수정 완료를 확인했다.
REV-P3-007 — Character URL이 계약 query searchTerm 대신 search를 사용한다
- 심각도: Medium
- 상태: 수정 완료
- 관련 요구사항: PRD §7.1
- 관련 계약:
GET /api/v2/admin/ai-charactersquerysearchTerm,page,size - 소유 Task: 신규
P3-R5
관찰 내용
수정 전 API adapter에는 searchTerm을 전달하지만 브라우저 URL parser와 serializer는 별도 search key를 사용했다. 따라서 계약 이름의 직접 링크 ?searchTerm=루나는 검색 상태로 복원되지 않고 기존 테스트도 ?search=루나를 정상 규칙으로 승인했다.
근거
- 문서:
prd.md:147-150 - 코드:
src/features/characters/pages/CharacterListPage.tsx:26-43,55 - 테스트:
src/features/characters/tests/character-list.test.tsx:50-66,103
재현 또는 검증 절차
/ai-characters?searchTerm=루나&page=1&size=20으로 직접 진입한다.- 검색 input이 빈 값이고 API request의
searchTerm이 복원되지 않는지 확인한다. - UI에서 검색하면 URL이
search=...로 생성되는지 확인한다.
영향 및 권장 수정 방향
공유·새로고침 URL과 계약 추적성이 어긋나며 외부에서 생성한 정상 계약 deep link가 무시된다. URL read/write key만 searchTerm으로 맞추고 직접 진입·pagination 왕복 test를 추가한다.
판정 기록:
- 2026-07-31 — PRD·OpenAPI query 계약과 구현·테스트의
searchURL key를 대조해 확정. - 2026-07-31 —
P3-R5에서 Character URL read/write key를searchTerm으로 정렬하고 Character focused unit·정적 Gate로 수정 완료를 확인했다.
판정 기록: 2026-07-31 — PRD·OpenAPI·URL code/test를 대조해 확정.
REV-P3-008 — Character 원본 image를 crop 적용 전에 제출할 수 있다
- 심각도: High
- 상태: 수정 완료
- 관련 요구사항:
FILE-007,FILE-009~010, PRD §10.5 - 관련 계약: Character create 필수
image, update optionalimagemultipart part - 소유 Task: 신규
P3-R6
관찰 내용
생성·수정의 selectImage는 파일 선택 즉시 raw File을 committed image state에 넣은 뒤 crop source를 비동기로 준비한다. 생성 form은 준비·dialog 상태를 submit guard에 포함하지 않고 crop 취소도 raw image를 지우지 않는다. 따라서 원본 제출 또는 취소한 파일 제출이 가능하다. 수정도 source 준비가 끝나기 전 raw replacement를 보낼 수 있고 준비 reject·연속 선택의 stale resolution을 처리하지 않는다.
근거
- 문서:
prd.md:346-361 - 코드:
src/features/characters/pages/CharacterCreatePage.tsx:63-92,103-127,165-180 - 코드:
src/features/characters/pages/CharacterEditPage.tsx:78-109,120-145,184-205 - 테스트가 승인하는 회귀:
src/features/characters/tests/CharacterCreatePage.test.tsx:147-170은 crop 적용 없이 선택한 raw file이 multipart에 포함되는 것을 기대한다.
재현 또는 검증 절차
- source 준비 Promise를 pending으로 둔 채 Character 생성 필수 text를 채우고 valid PNG를 선택한다.
생성을 누르면 crop 적용 전 raw PNG가 request에 포함될 수 있다.- source 준비 뒤 crop dialog에서 취소하고 생성해도 같은 raw file이 남는다.
- 수정 화면에서도 느린 첫 선택과 빠른 둘째 선택을 교차 resolve하면 마지막 선택이 아닌 dialog가 열릴 수 있다.
영향
1:1·최대 800px 보장을 거치지 않은 원본이나 사용자가 취소한 파일이 서버에 저장될 수 있어 media 무결성과 운영자 의도를 직접 위반한다.
권장 수정 방향
raw selection과 crop 적용 결과를 분리하고 마지막 selection token, 준비 상태, reject 오류, 준비/dialog 중 submit guard를 둔다. create 취소는 새 선택을 제거하고 edit 취소는 기존 서버 media를 유지해야 한다.
판정 기록:
- 2026-07-31 — PRD file 정책, 비동기 state 전이, 현재 통과 test를 함께 대조해 확정.
- 2026-07-31 —
P3-R6에서 create/edit committed image를 crop apply 결과로 한정하고 pending/cancel/stale/reject lifecycle test를 추가했다.npm run test:run -- src/features/characters는 6 files / 43 tests passed이고npm run typecheck,npm run lint,npm run build,git diff --check는 모두 성공했다. E2E는 사용자 지시에 따라 최종 회귀 단계로 이연해 수정 완료 판정.
plan·goal 전환 및 종료 판정
REV-P3-006→P3-R4REV-P3-007→P3-R5REV-P3-008→P3-R6- 최종 결론: High 1건·Medium 2건 모두 수정 완료. Blocker 없음.
- 교차 QA: 당시 Mobile Safari dirty-leave 실패는 focused 1회와
--repeat-each=5에서 모두 통과했고, 이후 지원 project 축소로 현재 Task에서 제외했다.
11. 최종 Phase별 점검 — 2026-07-31
- 검토 범위: Character 목록 query, 상세 workspace, create/update/deactivate payload, inactive read-only, image 정책·crop lifecycle을
CHAR-*, OpenAPI,P3와 대조했다. - 실행 증거:
npm run test:run -- src/features/characters6 files / 43 tests 통과. mock Chromium의 Character workspace·교차 resource·접근성 흐름도 전체 57 tests 안에서 통과했다. - 판정:
REV-P3-006~008수정 완료 상태가 유지되며 Phase 3 확정 신규 발견 사항 없음. - 남은 위험: 실제 개발 API Character mutation 수동 QA는 별도 Gate 상태다.
- 신규 Task: 없음.
12. 종합 재점검 — 2026-07-31
- 검토 범위: Character 목록·상세·생성·수정·비활성화, original work lookup, dirty-leave와 image 적용 lifecycle을 PRD
CHAR-*·OpenAPI·현재 test에 재대조했다. - 실행 증거: Character를 포함한 도메인 묶음 36 files / 206 tests passed. 현재 mock Chromium·Mobile Chrome 전체 Gate 109 passed / 5 skipped에도 Character 흐름이 포함됐다.
- 판정: Character endpoint·DTO·화면 소유의 확정 신규 발견 사항 없음.
- 교차 Phase: Character도 영향을 받는 crop frame/좌표와 Blob URL 문제는 공용 컴포넌트 소유
REV-P1-016~017/P1-R10~R11에서 수정한다. - 남은 위험: 실제 개발 API Character mutation 수동 QA와
P9-R7의 Safari dirty-leave 재검증이 필요하다. - 신규 Phase 3 Task: 없음.
13. 요청 기준 재리뷰 — 2026-07-31
- 검토 범위: Character list/search/detail/create/update/deactivate, original work lookup, optional field serialization, crop lifecycle와 workspace capability.
- 실행 증거: 관련 unit은 전체 실행에서 통과했고 mock 4-project matrix의 Character journey·axe·zoom·read-only 흐름도 0 failure였다. OpenAPI 2.3.0 parse·schema ref 점검도 통과했다.
- 판정: Phase 3 소유의 확정 신규 발견 사항 없음.
- 남은 위험: 실제 개발 API Character mutation·active-only 결과와 실기기 Safari QA는 자동 mock 검증과 분리된 수동 QA다.
- 신규 Phase 3 Task: 없음.
14. 최종 재검증 — 2026-07-31
- 검토 범위: Character list/search/detail/create/update/deactivate, original work lookup, optional field serialization, image lifecycle와 workspace capability.
- 실행 증거: 전체 unit 394 tests와 4-project mock matrix의 Character·axe·320px/200% zoom 시나리오가 0 failure였고 OpenAPI schema ref 검사도 통과했다.
- 판정: Phase 3 소유의 확정 신규 발견 사항 없음.
- 남은 위험: 실제 개발 API Character mutation·active-only 결과와 실기기 Safari QA.
- 신규 Phase 3 Task: 없음.
15. 2026-07-31 문서 기준 재리뷰
- 검토 범위: Character list/search/detail/create/update/deactivate, original work lookup, optional field serialization, inactive read-only과 image lifecycle를
CHAR-001~018·OpenAPI·P3에 대조했다. - 실행 증거: Character workspace·mutation을 포함한 실패 후보 5-spec focused는 5 files / 25 tests passed, server E2E는 36 passed, type·lint·build는 exit 0이었다. full unit 5 failure는
REV-P9-009/P9-R9로, WebKit direct-route navigation 경합은 동일 focused 통과 후REV-P9-010/P9-R10으로, Character/Audio 이미지에도 영향을 주는 공용 crop frame은REV-P1-019/P1-R13으로 분리했다. - 판정: Character endpoint·DTO·workspace 소유의 확정 신규 발견 사항 없음. 후보·오탐·보류 0건, 신규 Phase 3 Task 없음.
- 남은 위험: 실제 Character mutation·active-only 수동 QA와 공용
P1-R13~R15,P9-R9~R10완료 후 회귀가 필요하다.