Files

19 KiB

Phase 8 Comments 코드 리뷰·QA

1. 리뷰 정보

항목 내용
리뷰 대상 Phase 8 / Audio·Community Comments 2단계 thread와 mock/server integration
기준 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
리뷰 상태 판정 완료, 회귀 수정 완료 및 외부 수동 QA 대기

2. 리뷰 목적과 범위

목적

  • Comments의 정확한 2단계 구조, 작성자 기반 수정 권한, row 단위 soft delete와 target 격리를 검증한다.
  • mock store가 실제 OpenAPI 및 PRD 불변식을 의미 있게 재현하는지 확인한다.

포함 범위

  • 코드: src/features/comments, src/shared/mocks/comment-*
  • 테스트: Comments contract/UI/E2E와 mock store 간접 검증
  • 문서: COMMENT-001~COMMENT-009, MOCK-005, Comments OpenAPI operation, P8-*
  • 수동 검증: create/update/delete store 조건과 fixture writer/creator 대조

제외 범위

  • 외부 server DB의 cascade·ownership 실제 동작
  • API origin 불일치 수정과 실제 server Comments E2E 재실행

3. 판정 기준

심각도 기준
Blocker 실제 데이터 손실·보안 위험 또는 핵심 댓글 흐름 불능
High 확정 댓글 계약·권한·삭제 의미를 mock/검증이 위반하는 주요 회귀
Medium 일부 target·작성자·depth 조건의 기능 오류
Low 비핵심 표시·문서 정합성 문제

상태는 후보, 확정, 오탐, 보류, 수정 완료를 사용한다.

4. 검토한 근거

문서와 코드

  • 요구사항: COMMENT-002, COMMENT-003, COMMENT-004, COMMENT-007, MOCK-005
  • 계약: Audio·Community comments POST/PUT/DELETE, DELETE는 해당 row만 비활성화하고 자식 상태를 바꾸지 않음
  • 계획: P8-T1~P8-GATE
  • 코드: src/shared/mocks/comment-mock-store.ts:34-37,65-83
  • 테스트: src/features/comments/tests/comment-contract.test.ts, tests/e2e/comments.spec.ts

실행 환경

macOS 26.0 / Node v24.12.0 / npm 11.7.0
Vitest + Playwright 4 projects

실행한 검증

명령 또는 수동 검증 결과 핵심 증거
npm run test:run 성공 72 files, 354 tests passed
npm run e2e:mock 성공 P8-R2 후 Comments mock E2E 10 passed / 2 skipped
npm run e2e 실패 Phase 0 origin 불일치로 Comments 실제 server 검증 전 단계 Gate 실패
npm run typecheck / npm run lint / npm run build 성공 모두 exit 0
mock store mutation contract 성공 P8-R2 후 reply-parent 거부, fan PUT 거부, row-only delete 보존 contract 통과

5. 발견 사항 요약

ID 심각도 상태 제목 소유 Task 후속 goal
REV-P8-003 High 수정 완료 Comments mock store가 depth·수정 권한·row-only 삭제 불변식을 위반한다 P8-R2 P8-R2
REV-P8-004 Medium 수정 완료 Comments adapter가 FanTalk 전용 size=20..50 clamp를 공통 pagination에 적용한다 P8-R3 P8-R3

6. 발견 사항 상세

REV-P8-003 — Comments mock store가 depth·수정 권한·row-only 삭제 불변식을 위반한다

  • 심각도: High
  • 상태: 수정 완료
  • 관련 요구사항: COMMENT-002~COMMENT-004, COMMENT-007, MOCK-005
  • 관련 계약: Comments create/update/delete operation
  • 소유 Task: 신규 P8-R2

관찰 내용

mock store는 새 댓글의 parentId가 같은 target에 속하는지만 검사해 직접 답글 ID를 부모로 넣은 3단계 댓글을 허용한다. update는 target과 ID만 일치하면 작성자와 무관하게 수정한다. delete는 선택 row뿐 아니라 parentId === commentId인 모든 직접 답글을 함께 제거한다.

근거

  • 코드: src/shared/mocks/comment-mock-store.ts:34-37 — parent가 활성 root인지 확인하지 않음
  • 코드: src/shared/mocks/comment-mock-store.ts:65-75writerId === creatorId 확인 없음
  • 코드: src/shared/mocks/comment-mock-store.ts:78-83 — root와 직접 답글을 함께 filter
  • 문서: prd.md:338-344, prd.md:849
  • 계약: api-contract.openapi.json Comments DELETE 설명은 해당 row만 비활성화하고 자식 댓글 상태를 바꾸지 않음
  • 테스트 누락: UI에서 팬 수정 버튼이 없는지만 검증하며 직접 API fan PUT 거부, reply-parent POST 거부, root DELETE 후 reply 보존을 검증하지 않음

재현 또는 검증 절차

  1. 같은 target의 기존 직접 답글 ID를 parentId로 POST한다.
  2. mock store가 성공 처리해 3단계 row를 만드는 것을 확인한다.
  3. writerId !== creatorId인 팬 댓글 ID로 PUT한다.
  4. mock store가 성공 처리하는 것을 확인한다.
  5. 답글이 있는 root를 DELETE하고 답글 목록도 함께 사라지는 것을 확인한다.
  6. 요구 결과는 각각 요청 거부, 팬 PUT 거부, root row만 삭제하고 자식 상태 보존이다.

영향

mock preview와 contract test가 실제 댓글 계약과 다른 데이터 구조·권한·삭제 의미를 승인한다. 서버 연동 전 핵심 회귀를 숨기고 root 삭제 시 자식 데이터 손실을 정상 동작처럼 보이게 한다.

권장 조치

parent가 같은 target의 활성 root인지, update 대상의 writerId가 target creator ID와 같은지 검사한다. delete는 해당 ID row만 제거한다. Audio·Community 양쪽에 3개 음성 contract test와 E2E 보존 assertion을 추가한다.

판정 기록

  • 2026-07-30 — PRD·OpenAPI와 mock store의 세 mutation branch를 직접 대조해 확정.

REV-P8-004 — Comments adapter가 FanTalk 전용 size=20..50 clamp를 공통 pagination에 적용한다

  • 심각도: Medium
  • 상태: 수정 완료
  • 관련 요구사항: PRD §11.1 공통 pagination, COMMENT-006~007
  • 관련 계약: OpenAPI 공통 Size parameter
  • 소유 Task: 신규 P8-R3

관찰 내용

Comments normalizeSizesize=1을 20으로, size=51을 50으로 바꾼다. 공통 계약은 default 20·minimum 1만 정의하고 maximum은 없으며 20..50 보정은 FanTalk에만 적용한다. 현재 UI는 size 20을 사용해 화면 회귀가 드러나지 않지만 adapter 입력 계약이 다르다.

근거

  • 코드: src/features/comments/api/comment-api.ts:27-33,48-50
  • 문서: prd.md:636
  • 계약: OpenAPI components.parameters.Size의 default 20, minimum 1, maximum 없음
  • 테스트: Comments contract test는 mock handler size=1을 사용하지만 API adapter가 생성하는 size=1·51 query를 검증하지 않음

재현 또는 검증 절차

  1. getRootComments(..., {size:1}) 또는 getReplies(..., {size:1})를 호출한다.
  2. 실제 request query가 size=20이 되는 것을 확인한다.
  3. size=51size=50이 된다.
  4. 요구 결과는 각각 size=1, size=51을 그대로 보내고 0 이하만 1로 보정하는 것이다.

영향

작은 page를 요청하는 소비자는 과다 데이터를 받고 50개를 넘는 page를 요청하는 소비자는 요청값과 다른 pagination 결과를 받는다. adapter와 OpenAPI contract test의 신뢰성이 낮아진다.

권장 조치

Comments normalization에서 maximum과 minimum 20을 제거하고 최소 1만 적용한다. root와 reply 양쪽에 1·51 경계 test를 추가한다.

판정 기록

  • 2026-07-30 — PRD·OpenAPI 공통 parameter와 adapter 계산을 대조해 확정.
  • 2026-07-30 — P8-R3에서 Comments size normalization을 default 20·minimum 1로 정렬하고 root/replies adapter contract test로 size=1·size=51 보존을 확인해 수정 완료로 판정했다.

7. 확정 항목의 plan·goal 전환

  • REV-P8-003plan-task.md 신규 P8-R2
  • goal objective: [P8-R2] Comments mock mutation의 2단계·작성자 수정·row-only delete 불변식을 복구한다.
  • REV-P8-004plan-task.md 신규 P8-R3
  • goal objective: [P8-R3] Comments pagination을 공통 size 최소값 계약과 정렬한다.

8. 리뷰 종료 판정

판정 항목 결과 근거
리뷰 범위 전체 확인 충족 Comments code·test·PRD·OpenAPI 대조
후보 항목 판정 완료 충족 기존 1건 수정 완료, 신규 1건 수정 완료
확정 항목 plan 반영 충족 P8-R2, P8-R3 완료
확정 항목 수정 충족 P8-R2, P8-R3 완료
보류 항목의 담당·재개 조건 기록 해당 없음 보류 없음
검증 명령과 결과 기록 충족 §4

최종 결론: 확정 발견 사항 수정 완료.

남은 항목: 실제 개발 API Comments 수동 QA.

9. 수정 후 검증 기록

  • 수정 전 기록 — 2026-07-30: 아직 수정하지 않았다. P8-R2 완료 시 검증 결과를 누적한다.

P8-R2 수정 후 검증 — 2026-07-30

  • 무엇을: root/direct reply 2단계 생성, AI 작성 row 수정, 대상 row만 삭제하는 Comments mock mutation 불변식을 복구했다.
  • 왜: reply를 부모로 둔 3단계 row 생성, fan row 직접 PUT, root DELETE의 자식 row 제거가 mock preview와 contract test에서 실제 계약 위반을 승인하고 있었다.
  • 어떻게:
    • RED: npm run test:run -- src/features/comments/tests/comment-contract.test.ts는 1 file / 3 failed / 3 passed였다. Audio·Community reply-parent POST와 fan PUT은 200으로 성공했고, Audio root DELETE 뒤 직접 reply 목록은 0건이었다.
    • GREEN: CommentMockStore가 target의 root parent만 생성 대상으로 허용하고, creator ID와 writer ID가 일치하는 row만 수정하며, DELETE는 대상 row만 제외하도록 수정했다. focused contract test는 1 file / 6 tests passed였다.
    • 회귀: npm run test:run -- src/features/comments src/shared/mocks는 8 files / 33 tests passed였다. npm run e2e:mock -- tests/e2e/comments.spec.ts --project=chromium은 3 passed, 전체 npm run e2e:mock -- tests/e2e/comments.spec.ts는 10 passed / 2 skipped였다. npm run typecheck, npm run lint, npm run build, git diff --check -- docs/20260725_AI캐릭터관리자웹/plan-task.md docs/20260725_AI캐릭터관리자웹/reviews/phase8-comments.md src/shared/mocks src/features/comments tests/e2e/comments.spec.ts는 모두 exit 0이었다.
    • 진단: src/features/comments/tests/comment-contract.test.ts, src/shared/mocks/comment-mock-store.ts LSP diagnostics는 모두 0건이었다.
  • 수동 확인: Chromium mock E2E에서 Audio create/reply/edit/delete, Community 320px controls, keyboard-only Audio create flow를 실행해 모두 통과했다. UI의 fan PUT 0-request assertion도 기존 E2E로 유지했다.
  • 2026-07-30 — 2차 계약 점검에서 Comments size=1→20, size=51→50 보정을 확인했다. 애플리케이션 코드는 수정하지 않고 P8-R3로 전환했다.

P8-R3 수정 후 검증 — 2026-07-30

  • 무엇을: Comments adapter의 size query normalization에서 FanTalk 전용 20..50 clamp를 제거하고 공통 pagination 계약인 default 20·minimum 1만 적용했다.
  • 왜: OpenAPI 공통 Size에는 maximum이 없고, 20..50 보정은 FanTalk 전용이므로 Comments root/replies 요청값을 바꾸면 안 되기 때문이다.
  • 어떻게: RED npm run test:run -- src/features/comments/tests/comment-contract.test.ts는 1 failed / 5 passed로 root size=120, replies size=5150으로 바뀌는 실패를 재현했다. 수정 후 같은 command는 1 file / 6 tests passed, npm run test:run -- src/features/comments는 2 files / 11 tests passed였다. npm run typecheck, npm run lint는 exit 0이었고 LSP diagnostics는 변경 파일 0건이었다. 개발 중 E2E는 사용자 지시에 따라 P10-R5 이후 최종 E2E로 미뤘다.

10. 2026-07-31 재점검

  • 범위: Audio·Community root/reply 2단계, AI 작성 row PUT, 작성자 무관 row-only DELETE, pagination과 target 격리를 PRD COMMENT-001~008 및 OpenAPI 2.3.0에 재대조했다.
  • 검증: 전체 unit 72 files / 360 tests와 server allowlist 36 tests가 통과했다. 전체 Mock matrix의 Comments 시나리오도 각 project에서 통과하거나 문서화된 keyboard platform-policy skip만 발생했다.
  • 판정: Phase 8 신규 구현 결함은 없다. 공통 ResourcePagination ID 충돌은 shared UI 소유인 REV-P1-015/P1-R9로만 등록해 중복 Task를 만들지 않았다.
  • 문서 상태: 상단 리뷰 상태와 §5 요약의 REV-P8-004 상태가 §6·§8·§9의 수정 완료 기록과 일치하지 않는 문제는 REV-P10-007/P10-R6에 포함했다.
  • 남은 항목: 실제 개발 API Comments 수동 QA.
  • 신규 Task: 없음.

11. 최종 Phase별 점검 — 2026-07-31

  • 검토 범위: Audio·Community root/direct reply 2단계, 작성자별 수정·row-only DELETE, pagination과 cache 재조회를 COMMENT-*, OpenAPI, P8과 대조했다.
  • 실행 증거: npm run test:run -- src/features/comments 2 files / 11 tests, mock Chromium Comments 시나리오 3 tests가 통과했다.
  • 판정: Comments endpoint·payload·2단계 구조의 별도 신규 finding은 없다. runMutation의 동기 재진입 guard와 accessible 진행 표시 공백은 교차 품질 finding REV-P9-006/P9-R6에 귀속했다.
  • 남은 위험: 실제 개발 API Comments 수동 QA와 P9-R6 완료가 필요하다.
  • 신규 Phase 8 Task: 없음. 중복 Task 대신 P9-R6에서 추적한다.

12. 종합 재점검 — 2026-07-31

REV-P8-005 — 댓글 POST 실패 후 입력 초안이 성공처럼 초기화됨

항목 내용
심각도 Medium
상태 수정 완료
관련 요구사항·계약 PRD §10.5 오류·재시도 상태, COMMENT-001~006; OpenAPI mutation 성공 data=null/실패 error envelope
소유 Task P8-R4
코드 근거 src/features/comments/components/CommentForm.tsx:22~24, CommentThread.tsx:83~118
test 근거 Comments tests는 성공·pending은 검증하지만 루트/답글 POST 실패 후 textarea 값 보존과 같은 form 재시도를 검증하지 않는다.

재현 또는 검증 절차

  1. Audio 또는 Community 댓글에서 루트 댓글이나 답글을 입력한다.
  2. 첫 POST가 500 error를 반환하게 한다.
  3. runMutation이 오류를 화면에 저장한 뒤 reject를 소비하고 정상 resolve하는지 확인한다.
  4. CommentFormawait onSubmit 다음 줄에서 textarea를 비워 사용자가 입력한 내용을 잃는지 확인한다.

영향과 권장 조치

서버 오류는 표시되지만 운영자는 같은 내용을 바로 재시도할 수 없고 댓글을 다시 입력해야 한다. mutation callback이 명시적인 성공 여부를 반환하게 하고 성공한 POST에서만 form 값을 초기화한다. localStorage나 optimistic update는 추가하지 않는다.

판정 기록

  • 2026-07-31 — component 간 Promise contract와 실패 catch 경로를 정적으로 추적해 확정.
  • 2026-07-31 — Audio·Community에 중복 Task를 만들지 않고 Comments 소유 신규 P8-R4로 전환.
  • 2026-07-31 — P8-R4에서 댓글 생성 실패 후 초안 유지와 성공 후 초기화 contract를 단위 테스트로 고정해 수정 완료 판정.

Phase 8 결론

  • 자동 검증: Comments를 포함한 도메인 묶음 36 files / 206 tests, exact server E2E 18/18, 현재 두 project mock E2E 109 passed / 5 skipped가 통과했다.
  • 판정: endpoint·DTO·2단계 thread 구조는 통과했고 Medium 1건은 P8-R4에서 수정 완료됐다.
  • 남은 위험: 실제 개발 API Comments 수동 QA가 남아 있다. Safari/WebKit은 현재 지원 범위에서 제외한다.

P8-R4 수정 후 검증 — 2026-07-31

  • 무엇을: 루트 댓글과 열린 답글 form의 POST 실패 시 입력 초안을 유지하고, 같은 form 재시도 성공 후에만 textarea를 비우도록 수정했다.
  • 왜: runMutation이 실패를 내부 alert로 처리하면서도 CommentForm에는 성공처럼 resolve해 사용자의 초안을 잃게 했기 때문이다.
  • 검증: RED npm run test:run -- src/features/comments/tests/comment-thread.test.tsx는 1 failed / 5 passed로 실패 후 root textarea가 빈 값이 되는 문제를 재현했다. GREEN focused는 1 file / 6 tests passed, Comments 회귀는 npm run test:run -- src/features/comments 3 files / 13 tests passed였다. src/features/comments LSP diagnostics는 5 TSX files / 0 diagnostics였다.
  • E2E: 사용자 지시에 따라 개발 중 반복 E2E는 실행하지 않고 최종 회귀 단계에서 필요 시 실행한다.

13. 요청 기준 재리뷰 — 2026-07-31

  • 검토 범위: Audio·Community 2단계 thread, writer 권한, row-only delete, root/reply pagination, 실패 초안 보존과 pending 상태.
  • 실행 증거: Comments unit은 전체 실행에서 통과했고 4-project mock Comments journey는 platform 근거가 있는 keyboard skip 외 0 failure였다.
  • 판정: P8-R4까지의 수정 완료 상태가 유지되며 Phase 8 기능 소유의 신규 결함은 없다.
  • 문서 교차 항목: 상단 리뷰 상태가 최신 P8-R4 수정 완료 기록과 어긋나는 문제는 REV-P10-008/P10-R7에서 정리한다.
  • 남은 위험: 실제 개발 API Comments mutation·권한·cache 재조회 수동 QA.

14. 최종 재검증 — 2026-07-31

  • 검토 범위: Audio·Community 2단계 thread, writer 권한, row-only delete, pagination, 실패 초안 보존과 pending/error 상태.
  • 실행 증거: 전체 unit 394 tests와 4-project mock Comments 시나리오가 정책상 keyboard skip 외 0 failure였고 OpenAPI 10개 Comments operation·schema ref 검사도 통과했다.
  • 판정: Phase 8 소유의 확정 신규 발견 사항 없음.
  • 남은 위험: 실제 개발 API Comments mutation·권한·cache 재조회 수동 QA.
  • 신규 Phase 8 Task: 없음.

15. 2026-07-31 문서 기준 재리뷰

  • 검토 범위: Audio·Community 2단계 thread, target별 path/schema, writer 수정 권한, row-only delete, root/reply pagination, 초안 보존·pending/error를 COMMENT-001~008·OpenAPI·P8·P10-T6에 대조했다.
  • 실행 증거: Comments contract/thread/pending test는 full output에서 통과했고, OpenAPI 10개 Comments operation·schema ref·type·lint·build·server E2E 36 tests도 통과했다. full unit 비결정성은 REV-P9-009/P9-R9로 분리했다.
  • 판정: Comments endpoint·DTO·2단계 UI 소유의 확정 신규 발견 사항 없음. 후보·오탐·보류 0건, 신규 Phase 8 Task 없음.
  • 남은 위험: 실제 Comments mutation·권한·cache 재조회 수동 QA와 P9-R9 회귀가 남는다.