# Phase 6 Community Posts 코드 리뷰·QA ## 1. 리뷰 정보 | 항목 | 내용 | |---|---| | 리뷰 대상 | Phase 6 / Community 목록·생성·Sheet 수정·삭제 | | 기준 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` | | 리뷰 상태 | 판정 완료, `REV-P6-004` 수정 완료 및 외부 수동 QA 대기 | ## 2. 리뷰 목적과 범위 ### 목적 - Community pagination, multipart mutation, mobile read-only capability와 Sheet 흐름을 검증한다. - 목록 상태 label이 운영자 표시 규칙과 일치하는지 확인한다. ### 포함 범위 - 코드: `src/features/community-posts` - 테스트: Community contract/form/list/sheet/E2E - 문서: Community 요구사항, PRD §10·§13, `P6-*` - 수동 검증: desktop list의 상태 표시 정적 대조 ### 제외 범위 - Community Comments thread는 Phase 8에서 판정 - 외부 서버의 media upload·audio playback ## 3. 판정 기준 심각도는 `Blocker`, `High`, `Medium`, `Low`, 상태는 `후보`, `확정`, `오탐`, `보류`, `수정 완료`를 사용한다. 기능·계약 위반을 우선하고 비핵심 표시 문자열은 Low로 판정한다. ## 4. 검토한 근거 ### 문서와 코드 - 요구사항: Community 요구사항과 PRD §13의 중앙 formatter 규칙 - 계약: Community list/create/update/delete - 계획: `P6-T1`~`P6-GATE` - 코드: `src/features/community-posts/components/CommunityPostList.tsx:22-30` - 테스트: `src/features/community-posts/tests/community-list.test.tsx:84-95` ### 실행 환경 ```text macOS 26.0 / Node v24.12.0 / npm 11.7.0 Playwright 4 projects, mobile read-only와 768/1280px 관리 흐름 포함 ``` ### 실행한 검증 | 명령 또는 수동 검증 | 결과 | 핵심 증거 | |---|---|---| | `npm run test:run` | 성공 | 72 files, 354 tests passed | | `npm run e2e:mock` | 부분 실패 | Community 관련 실행 항목 통과 또는 프로젝트 정책상 skip; 전체 origin 실패는 `REV-P0-004` | | `npm run typecheck` / `npm run lint` / `npm run build` | 성공 | 모두 exit 0 | | Community list label 정적 대조 | 실패 | `성인 false/true` 원시 boolean 노출, unit test가 문자열 고정 | ## 5. 발견 사항 요약 | ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | |---|---|---|---|---|---| | `REV-P6-002` | Low | 수정 완료 | Community desktop 목록이 성인 여부를 `false/true`로 표시한다 | `P6-R2` | `P6-R2` | ## 6. 발견 사항 상세 ### REV-P6-002 — Community desktop 목록이 성인 여부를 `false/true`로 표시한다 - **심각도:** Low - **상태:** 수정 완료 - **관련 요구사항:** PRD §13 표시 formatter 일관성 - **관련 계약:** Community list item의 `isAdult` boolean - **소유 Task:** 신규 `P6-R2` **관찰 내용** desktop table의 플래그 열은 고정·댓글 여부는 한글 label로 변환하지만 성인 여부만 `String(post.isAdult)`로 출력한다. unit test도 “성인 false” 문구를 성공 조건으로 고정한다. **근거** - 코드: `src/features/community-posts/components/CommunityPostList.tsx:29` - 테스트: `src/features/community-posts/tests/community-list.test.tsx:93` - 문서: `prd.md:806` **재현 또는 검증 절차** 1. Community 목록을 desktop 폭에서 연다. 2. 일반 게시글의 상태 열에서 “성인 false”를 확인한다. 3. 성인 콘텐츠에서는 “성인 true”가 표시됨을 코드로 확인한다. 4. 요구 결과는 “일반 콘텐츠/성인 콘텐츠”처럼 운영자용 label을 표시하는 것이다. **영향** 비핵심 플래그이지만 화면의 다른 한글 상태와 어긋나며 운영자가 내부 boolean을 해석해야 한다. **권장 조치** 공통 boolean label 또는 Community formatter로 변환하고 unit/E2E assertion을 사용자 문구 기준으로 갱신한다. **판정 기록** - 2026-07-30 — 구현과 현재 성공 test가 원시 boolean을 의도적으로 노출함을 확인해 확정. - 2026-07-30 — `P6-R2`에서 Community 전용 status label helper를 row/card에 적용하고 unit·mock E2E로 raw `성인 true/false` 비노출을 확인해 수정 완료. ## 7. 확정 항목의 plan·goal 전환 - `REV-P6-002` → `plan-task.md` 신규 `P6-R2` - goal objective: `[P6-R2] Community 상태 boolean을 운영자용 label로 표시한다.` ## 8. 리뷰 종료 판정 | 판정 항목 | 결과 | 근거 | |---|---|---| | 리뷰 범위 전체 확인 | 충족 | Community code·test·계약 대조 | | 후보 항목 판정 완료 | 충족 | 1건 수정 완료 | | 확정 항목 plan 반영 | 충족 | `P6-R2` | | 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 없음 | | 검증 명령과 결과 기록 | 충족 | §4, §9 | **최종 결론:** 수정 검증 완료 **남은 항목:** `REV-P6-002`/`P6-R2` 범위 없음. P6-GATE server mode integration은 기존 외부 의존·server-mode 대상 spec 정합성 항목으로 별도 유지한다. ## 9. 수정 후 검증 기록 ### 2026-07-30 — `P6-R2` 수정 검증 | 명령 또는 수동 검증 | 결과 | 핵심 증거 | |---|---|---| | `npm run test:run -- src/features/community-posts` | 성공 | 5 files, 42 tests passed. Unit test가 desktop/mobile status label 2건과 raw `성인 false` 비노출을 검증한다. | | `npm run e2e:mock -- tests/e2e/community.spec.ts --project=chromium` | 성공 | 7 passed. Mobile card와 768/1280px desktop table에서 `일반 콘텐츠`/`성인 콘텐츠` label 및 raw `성인 true/false` 비노출을 검증한다. | | `npm run e2e:mock -- tests/e2e/community.spec.ts` | 성공 | 24 passed / 4 skipped. Skipped 항목은 기존 Chromium audio metadata 한정과 WebKit range input focus 제외 정책이다. | | LSP diagnostics | 성공 | `src/features/community-posts` directory scan 10 files, 0 diagnostics. `tests/e2e/community.spec.ts`도 0 diagnostics. | **판정:** `REV-P6-002`는 수정 완료. API schema, request payload, mock mutation shape 변경은 없다. ## 10. 2차 점검 결과 — 2026-07-30 - 전체 unit 72 files / 358 tests와 mock Chromium matrix 57 tests에서 Community 흐름이 통과했다. - Phase 6 신규 발견은 없으며 `REV-P6-002` 수정 완료 상태가 유지된다. ## 11. 2026-07-31 재점검 ### 실행·판정 요약 - **범위:** Community 목록·Sheet·create/edit/fix/deactivate·media·Comments 결합을 PRD `COMMUNITY-001~015`, §10.4와 OpenAPI 2.3.0에 재대조했다. - **검증:** 전체 unit 72 files / 360 tests, server allowlist 36 tests, typecheck·lint·build가 통과했다. - **신규 발견:** Medium 1건. 기존 `REV-P6-002`는 수정 완료 상태를 유지한다. | ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | |---|---|---|---|---|---| | `REV-P6-003` | Medium | 수정 완료 | Community 게시글 비활성화가 확인 없이 즉시 실행된다 | `P6-R3` | `P6-R3` | ### REV-P6-003 — Community 게시글 비활성화가 확인 없이 즉시 실행된다 - **심각도:** Medium - **상태:** 수정 완료 - **관련 요구사항:** PRD §10.4, `COMMUNITY-005` - **관련 계약:** Community post DELETE soft delete - **소유 Task:** 신규 `P6-R3` **관찰 내용** Sheet의 `비활성화` 버튼은 `deletePost`를 직접 호출해 첫 click에 DELETE를 보낸다. Character·Audio·Series는 같은 영향의 동작에 `ConfirmDeactivateDialog`를 사용하지만 Community만 확인·영향 설명·취소 단계가 없다. 현재 unit/E2E도 첫 click 즉시 성공을 기대해 이 차이를 승인한다. **근거** - 문서: `prd.md:510-522` — 영향이 큰 비활성화는 AlertDialog 사용 - 코드: `src/features/community-posts/components/CommunityPostSheet.tsx:110-122,170-174` - 테스트: `src/features/community-posts/tests/community-sheet.test.tsx:68-73`, `tests/e2e/community.spec.ts:155` **재현 또는 검증 절차** 1. active Community 게시글 Sheet를 연다. 2. `비활성화`를 한 번 누른다. 3. 확인 dialog 없이 즉시 DELETE와 Sheet close가 실행되는지 확인한다. **영향** 오조작 한 번으로 게시글이 active-only 목록에서 사라지며 현재 UI에는 복원 기능이 없다. 동일한 soft delete 정책의 다른 도메인과 안전 경계도 불일치한다. **권장 수정 방향** 기존 공통 `ConfirmDeactivateDialog`를 재사용해 영향 설명·취소·confirm·pending/error를 연결하고, 확인 전·취소 후 request 0건과 confirm 연타 1건을 test로 고정한다. **판정 기록:** - 2026-07-31 — PRD UI 원칙과 도메인별 deactivate 구현을 대조해 확정. - 2026-07-31 — `P6-R3`에서 공통 `ConfirmDeactivateDialog`를 연결하고, 첫 click·취소 후 request 0건과 confirm 연타 시 단일 soft delete를 unit test로 고정해 수정 완료. ### plan·goal 전환 및 종료 판정 - `REV-P6-003` → `plan-task.md` 신규 `P6-R3` - **최종 결론:** 신규 Medium 1건 수정 완료. Blocker/High 없음. - **공통 UI:** Community/Comments 화면의 pagination ID 충돌은 `REV-P1-015`/`P1-R9`이 소유한다. ### 2026-07-31 — `P6-R3` 수정 검증 | 명령 또는 수동 검증 | 결과 | 핵심 증거 | |---|---|---| | `npm run test:run -- src/features/community-posts/tests/community-sheet.test.tsx` | 성공 | 1 file, 7 tests passed. 첫 click 확인 dialog, 취소 focus 복귀, confirm 연타 단일 soft delete를 검증한다. | | `npm run test:run -- src/features/community-posts` | 성공 | 5 files, 42 tests passed. Community feature 회귀가 통과했다. | | `npm run typecheck` / `npm run lint` / `npm run build` | 성공 | 모두 exit 0. Production build는 기존 chunk size warning만 출력했다. | | `git diff --check` | 성공 | whitespace error 없음. | | LSP diagnostics | 성공 | `src/features/community-posts` directory scan 10 files, 0 diagnostics. | **판정:** `REV-P6-003`는 수정 완료. E2E는 개발 중 매 Task 실행하지 않는 사용자 지시에 따라 전체 Task 완료 후 필요 시 수행한다. ## 12. 최종 Phase별 점검 — 2026-07-31 - **검토 범위:** Community 목록·create/update/fixed/deactivate, attachment file 정책, list-backed Sheet, 댓글 연결을 `COMMUNITY-*`, OpenAPI, `P6`와 대조했다. - **실행 증거:** `npm run test:run -- src/features/community-posts` 5 files / 42 tests, mock Chromium Community 시나리오 7 tests가 통과했다. - **판정:** Community payload·file 정책의 별도 신규 finding은 없다. create/update/fixed mutation의 동기 재진입 guard와 진행 표시 공백은 교차 품질 finding `REV-P9-006`/`P9-R6`에 귀속했다. - **남은 위험:** 실제 개발 API Community 수동 QA와 `P9-R6` 완료가 필요하다. - **신규 Phase 6 Task:** 없음. 중복 Task 대신 `P9-R6`에서 추적한다. ## 13. 종합 재점검 — 2026-07-31 - **검토 범위:** Community pagination·create/update/fixed/deactivate·attachment/crop 정책·Sheet·Comments 연결을 재대조했다. - **실행 증거:** Community를 포함한 도메인 묶음 36 files / 206 tests, exact server E2E 18/18, 현재 두 project mock E2E 109 passed / 5 skipped가 통과했다. - **판정:** Community endpoint·multipart payload·Sheet 소유의 확정 신규 finding은 없다. - **교차 Phase:** JPEG/PNG crop 문제는 `REV-P1-016~017`, 댓글 POST 실패의 초안 소실은 `REV-P8-005`에서 공통 수정한다. - **남은 위험:** 실제 개발 API Community mutation 수동 QA가 남아 있다. WebKit 계열 Gate는 현재 지원 범위에서 제외한다. - **신규 Phase 6 Task:** 없음. ## 14. 요청 기준 재리뷰 — 2026-07-31 - **검토 범위:** Community pagination, create/update/fixed/deactivate, JPEG/PNG/GIF·audio attachment, Sheet와 Comments 연결. - **실행 증거:** 관련 unit과 4-project mock Community journey·responsive·axe가 0 failure였고 OpenAPI pagination/multipart schema ref 점검도 통과했다. - **판정:** Phase 6 소유의 확정 신규 발견 사항 없음. - **남은 위험:** 실제 개발 API Community mutation·active-only·media reject 수동 QA. - **신규 Phase 6 Task:** 없음. ## 15. 최종 재검증 — 2026-07-31 - **검토 범위:** Community pagination, create/update/fixed/deactivate, media·crop policy, Sheet와 Comments 연결. - **실행 증거:** 전체 unit 394 tests와 4-project mock Community·responsive·axe 시나리오가 0 failure였고 OpenAPI pagination/multipart schema ref 검사도 통과했다. - **판정:** Phase 6 소유의 확정 신규 발견 사항 없음. - **남은 위험:** 실제 개발 API Community mutation·active-only·media reject 수동 QA. - **신규 Phase 6 Task:** 없음. ## 16. 2026-07-31 문서 기준 재리뷰 ### 검토 범위와 제외 - **검토:** PRD `COMMUNITY-*`, `FILE-001~015`, OpenAPI Community list/multipart operation, create/Sheet media lifecycle, GIF/JPEG/PNG 정책과 test를 current working tree에서 대조했다. - **제외:** 실제 server의 GIF width·MIME 거부와 memory profiler를 이용한 장시간 사용 계측은 credential·실기기 수동 QA로 남겼다. ### `REV-P6-004` GIF dimension 검사용 Blob URL 미해제 | 항목 | 내용 | |---|---| | 심각도 | Medium | | 상태 | 수정 완료 | | 관련 요구사항 | `FILE-003`, `FILE-011`, `FILE-014` | | 소유 Task | `P6-R4` | **근거** - `src/features/community-posts/validation/community-post-media-policy.ts:60`에서 `createCropSource(file)`로 preview Blob URL과 `release`를 소유한 source를 만든다. - 같은 파일 `:61-64`의 GIF 분기는 width가 800px 이하이면 `ready`, 초과면 `error`를 반환하면서 `source.release?.()`를 호출하지 않고 source도 반환하지 않는다. - `CommunityPostForm.tsx:51,66-69`과 `CommunityPostSheet.tsx:34,63-66`의 cleanup은 `prepared.kind === "crop"`인 JPEG/PNG source만 인수받는다. GIF에서 손실된 release를 호출할 수 없다. - 현재 Community test는 valid/oversized GIF의 선택·제출 결과는 검증하지만 release 호출 횟수를 직접 검증하지 않는다. **재현·영향** 1. valid GIF 또는 800px 초과 GIF를 선택할 때마다 `createImageCropSource()`가 만든 object URL이 해제되지 않는다. 2. 게시물 작성·수정에서 대용량 GIF를 반복 교체하면 tab의 memory 사용량이 불필요하게 유지될 수 있다. 저장 file 변조·animation 손실은 확인되지 않았다. **권장 조치·판정 기록** - GIF width 판정 직후 `ready`/거부 반환 전에 source를 해제하고, valid·oversized GIF에 release 각 1회, JPEG/PNG crop handoff에 조기 release 0회를 검증한다. - 2026-07-31 — source 소유권 data flow를 정적 추적해 Medium 확정. 완료된 `P1-R11`을 열지 않고 Community GIF 특수 분기 소유의 신규 `P6-R4`로 전환했다. 제품 코드는 수정하지 않았다. - 2026-07-31 — `P6-R4`에서 `prepareCommunityPostImage()` GIF width 판정 후 source release를 호출하도록 수정했다. RED 1 failure 재현 후 media policy·form·Sheet focused 3 files / 18 tests, Community 회귀 7 files / 45 tests, `typecheck`, `lint`, 개발/운영 build가 통과했고 reviewer blocker였던 release 순서도 `width → release` test로 보강해 delta review `APPROVED`를 받았다. E2E와 반복 GIF 수동 확인은 전체 Task 구현 후 필요 시 수행한다. ### 재리뷰 검증·종료 판정 - **자동 증거:** `npm run typecheck`, `npm run lint`, 개발/운영 build, server E2E 36 tests는 통과했다. 전체 unit은 79 files / 397 tests 중 5 failed / 392 passed였고, Community Sheet를 포함한 후보 5개 spec focused는 5 files / 25 tests passed였다. 비결정성은 `REV-P9-009`/`P9-R9`로 분리했다. - **판정:** Medium 1건을 확정해 `P6-R4`로 전환했고 수정 완료했다. 오탐·보류로 남은 후보는 없다. - **남은 위험:** 실제 개발 API media reject·active-only mutation QA와 반복 GIF 선택 memory 계측은 `P6-R4`·수동 QA 완료 전까지 남는다. ## 17. 수정 결과 재리뷰 — 2026-07-31 - **검토:** `prepareCommunityPostImage()`의 valid/oversized GIF release와 JPEG/PNG crop source handoff를 구현·test에서 재대조했다. - **검증:** `npm run test:run -- src/features/community-posts/tests/community-post-media-policy.test.ts src/features/community-posts/tests/community-form.test.tsx src/features/community-posts/tests/community-sheet.test.tsx` — exit 0, 3 files / 18 tests passed. 전체 unit도 두 차례 연속 81 files / 409 tests passed했고 typecheck·lint·개발/운영 build가 통과했다. Community를 포함한 Chromium 4-spec focused mock E2E도 31 tests passed였다. - **판정:** `REV-P6-004` 수정은 유지됐다. Phase 6 소유의 추가 확정 발견 사항은 없다. - **남은 위험:** 실제 개발 API media reject·active-only mutation QA와 장시간 반복 선택 memory 계측은 수동 범위다.