From c8636a22f131de97eb0876b561b50067a548806d Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Mon, 10 Aug 2026 18:44:14 +0900 Subject: [PATCH] =?UTF-8?q?docs(creator):=20=EC=BB=A4=EB=AE=A4=EB=8B=88?= =?UTF-8?q?=ED=8B=B0=20=EC=9E=A0=EA=B8=88=20=EA=B8=B0=EB=A1=9D=EC=9D=84=20?= =?UTF-8?q?=EA=B0=B1=EC=8B=A0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan-task.md | 172 ++++++++++++++++-- .../prd.md | 38 +++- .../reviews/phase3-community-item-unification.md | 69 ++++++- 3 files changed, 247 insertions(+), 32 deletions(-) diff --git a/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/plan-task.md b/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/plan-task.md index 34672f9d..1880c35b 100644 --- a/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/plan-task.md +++ b/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/plan-task.md @@ -2,9 +2,9 @@ > **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:executing-plans` to implement this plan task-by-task. 프로젝트 보수적 실행 모드에 따라 multi-agent는 사용하지 않는다. -**Goal:** 홈과 커뮤니티 탭의 커뮤니티 아이템 외형·상세 진입·더보기를 통일하고 구매는 상세에서만 처리하며, 표시할 reaction이 없을 때 빈 공간을 제거한다. +**Goal:** 홈과 커뮤니티 탭의 커뮤니티 아이템 외형·상세 진입·더보기를 통일하고 구매는 상세에서만 처리하며, 유료 미구매 상태의 reaction·댓글·더보기 노출을 차단한다. -**Architecture:** 홈 API 게시글을 private 로컬 변환으로 `CreatorChannelCommunityPostItem`에 연결해 기존 `CreatorChannelCommunityListItem`을 재사용한다. 목록 구매 상태·dialog는 상세 ViewModel·View로 옮기고 기존 구매 API를 재사용하며, 변경 성공 후 홈·커뮤니티 목록을 동기화한다. 목록·홈 공용 아이템과 상세는 유료 잠금 상태에서 reaction bar 전체를 조건부로 제외한다. +**Architecture:** 홈 API 게시글을 private 로컬 변환으로 `CreatorChannelCommunityPostItem`에 연결해 기존 `CreatorChannelCommunityListItem`을 재사용한다. 목록 구매 상태·dialog는 상세 ViewModel·View로 옮기고 기존 구매 API를 재사용하며, 변경 성공 후 홈·커뮤니티 목록을 동기화한다. 목록·홈 공용 아이템과 상세는 유료 잠금 상태에서 reaction bar를 제외하고, 공용 리스트 아이템은 더보기도 제외한다. 상세 View·ViewModel은 하나의 댓글 상호작용 상태로 댓글 UI와 mutation을 차단한다. **Tech Stack:** Swift, SwiftUI, Combine, Moya, Kingfisher, SDWebImageSwiftUI, Xcode workspace `SodaLive.xcworkspace`. @@ -17,6 +17,7 @@ - 목록의 리스트형·썸네일형 구매 호출은 제거하고 기존 구매 dialog·API 호출을 상세로 옮긴다. - 유료 미구매 영역은 홈·리스트형·썸네일형·상세 모두 `Color.gray800`을 사용한다. - 하트·댓글을 모두 숨기는 유료 미구매 상태에서는 reaction bar의 고정 높이와 부모 spacing을 남기지 않는다. +- 상세의 유료 미구매 상태에서는 댓글 구분선·목록·입력창을 표시하지 않고 작성·수정·삭제 API 요청을 시작하지 않는다. - 현재 실행 가능한 단일 test bundle이 없으므로 신규 test 타겟을 만들지 않고 정적 contract check·Preview·Debug build·시뮬레이터로 검증한다. - 사용자가 직접 요청하지 않았으므로 `git commit`은 실행하지 않는다. @@ -24,24 +25,24 @@ | 문서 항목 | 내용 | |---|---| -| 상태 | reaction bar 회귀 수정·빌드 완료·외부 통합 검증 대기 | +| 상태 | `P2-R3` 상태별 수동 검증 완료·`P2-R4` 코드/자동 검증 완료·상태별 수동 확인 대기 | | 작성일 | 2026-08-10 | | 요구사항 기준 | `docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md` | | API 기준 | `GET /api/v2/creator-channels/{creatorId}/home` 필드 확장·기존 커뮤니티 목록/상세 API | -| 현재 Phase | Phase 3 Gate | -| 현재 활성 Goal | `P3-T1`·`P3-GATE`—`EXT-001`·부팅된 Simulator 대기 | +| 현재 Phase | Phase 2 | +| 현재 활성 Goal | `P2-R4`—더보기 상태별 수동 확인 | ## 목표 -홈 요약·커뮤니티 리스트·썸네일에서 게시글 터치 결과와 잠금 스타일을 통일하고, 더보기 권한 메뉴와 상세 구매 흐름을 정확하게 유지한다. +홈 요약·커뮤니티 리스트·썸네일에서 게시글 터치 결과와 잠금 스타일을 통일하고, 더보기 권한 메뉴와 상세 구매 흐름을 정확하게 유지한다. 상세의 유료 미구매 방문자에게는 reaction·댓글 영역을 표시하지 않고 댓글 mutation을 허용하지 않는다. ## 현재 상태 | Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | |---:|---|---:|---|---| | 1 | 완료 | `3/3` | `P1-GATE` 완료 | 없음 | -| 2 | 완료 | `3/3` | `P2-GATE` 완료 | 없음 | -| 3 | 진행 | `0/1` | `P3-T1`, `P3-GATE` | `EXT-001` 반영·Simulator 부팅 | +| 2 | 진행 | `5/6` | `P2-R4` | 상태별 수동 확인 | +| 3 | 진행 | `0/1` | `P3-T1`, `P3-GATE` | `EXT-001` 반영 | ## 범위 @@ -52,6 +53,7 @@ - 홈 섹션의 `CreatorChannelCommunityListItem` 재사용 - 홈의 고정·수정·삭제·신고 메뉴 연결과 성공 후 목록 동기화 - 홈·커뮤니티 리스트와 상세에서 표시할 reaction이 없을 때 빈 공간 제거 +- 커뮤니티 상세의 유료 미구매 상태에서 댓글 구분선·목록·입력창 미표시와 작성·수정·삭제 mutation 차단 - 정적 검사·Preview·Debug build·시뮬레이터 검증·문서 누적 ### 제외 @@ -61,6 +63,7 @@ - 커뮤니티 작성·상세·댓글의 범위 밖 리팩터링 - 신규 공용 모델·protocol·factory·dependency·test target 추가 - 백엔드 구현과 배포 +- 상세 API 댓글 payload 계약과 `CreatorChannelReplyDetail/**` 변경 ## 기술적 제약 @@ -69,6 +72,7 @@ - 홈·커뮤니티 변경 성공 후 갱신은 `CreatorChannelView`의 한 helper에서 두 ViewModel 재조회를 호출한다. - 사용자 노출 오류는 기존 `errorMessage`·`isShowPopup`·toast 흐름을 유지한다. - 홈 응답에 추가 필드가 없으면 decoding 실패를 기존 홈 API 실패로 처리하고 fallback 값을 만들지 않는다. +- 댓글 차단은 기존 유료 잠금 조건 `price > 0 && existOrdered == false && isOwnPost == false`와 상세의 `isCommentAvailable`을 함께 사용한다. ## Task 검증 규칙 @@ -335,11 +339,11 @@ Expected: whitespace 오류 0건, 필드 2건, 세 UI의 `Color.gray800`·상세 ## Phase 2: 홈 아이템 재사용과 메뉴 연결 -**Phase 결과:** 홈이 기존 커뮤니티 리스트 아이템·상세 route·권한 메뉴를 재사용하고 변경 성공 후 홈·커뮤니티 목록을 동기화한다. +**Phase 결과:** 홈이 기존 커뮤니티 리스트 아이템·상세 route·권한 메뉴를 재사용하고 변경 성공 후 홈·커뮤니티 목록을 동기화하며, 상세 유료 미구매 상태의 reaction·댓글 UI와 댓글 mutation을 차단한다. **선행조건:** `P1-GATE` 완료. -**Phase 완료 조건:** `P2-T1`, `P2-T2`, `P2-R1`, `P2-GATE` 완료. +**Phase 완료 조건:** `P2-T1`, `P2-T2`, `P2-R1`, `P2-R2`, `P2-R3`, `P2-R4`, `P2-GATE` 완료. ### Task 2.1 홈 섹션에 기존 리스트 아이템·상세·더보기 연결 @@ -614,6 +618,77 @@ Expected: 첫 명령 match 0건·exit code 1, 둘째 명령 필수 연결 match, - [x] **REFACTOR:** 공지 카드 외형·오디오 재생과 기존 상세·구매·refresh 구현은 변경하지 않는다. +### Follow-up Task 2.5 상세 유료 미구매 댓글 영역·동작 차단 + +**Goal 실행 `P2-R3`:** 커뮤니티 상세의 유료 미구매 잠금 상태에서 댓글 UI를 표시하지 않고 작성·수정·삭제 mutation을 실행하지 않는다. + +- **시작 조건:** 사용자 후속 요청·인터뷰 A안 확정, PRD `CCCI-011`·`DEC-015` 반영. +- **완료 증거:** 상세 댓글 구분선·목록·입력창의 단일 조건부 삽입, ViewModel 댓글 mutation guard, focused contract check, `git diff --check`, Debug build, 상태별 수동 확인. +- **범위 밖:** 기존 reaction bar 구현 재수정, 상세 API 댓글 payload 변경, `CreatorChannelReplyDetail/**`, 신규 공용 모델·test target 추가. + +**Files:** + +- Modify: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift` +- Modify: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` +- Test: 신규 test 파일 없음—focused contract check·Debug build·시뮬레이터 사용 + +**Interfaces:** + +- Consumes: `detail.price`, `detail.existOrdered`, `detail.isCommentAvailable`, 기존 `isOwnPost`, 댓글 전송·삭제 진입점. +- Produces: 유료 잠금과 댓글 허용 상태를 함께 판정하는 단일 ViewModel 상태, 잠금 상태에서 댓글 UI·mutation이 없는 상세 화면. + +- [x] **RED:** 상세 View가 유료 잠금 여부 없이 `isCommentAvailable`만으로 댓글 목록·입력창을 표시하고 ViewModel의 댓글 전송 가능 여부도 같은 필드만 확인함을 재현한다. + +```bash +rg -n "if detail\.isCommentAvailable|detail\?\.isCommentAvailable == true" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift +rg -n "guard detail\?\.isCommentAvailable == true|func sendComment|func confirmDelete" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift +``` + +Expected: 상세 View의 댓글 목록·입력창 조건과 ViewModel의 기존 전송 조건이 match. + +- [x] **GREEN:** ViewModel에 기존 유료 잠금 조건과 `isCommentAvailable`을 결합한 단일 댓글 상호작용 상태를 추가한다. +- [x] **GREEN:** 상세 View의 댓글 구분선·목록·하단 입력창은 해당 상태가 `true`일 때만 삽입한다. +- [x] **GREEN:** `sendComment`와 `confirmDelete`가 같은 상태를 guard하고, `applyDetail`은 잠금 상태의 댓글·편집·삭제 임시 상태를 기존 댓글 비활성 분기와 동일하게 비운다. +- [x] **GREEN 확인:** 댓글 표시 조건과 mutation guard가 단일 상태를 사용하고 기존 직접 조건이 상세 View에서 제거됐는지 확인한다. + +```bash +rg -n "isCommentInteractionAvailable" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift +rg -n "if detail\.isCommentAvailable|detail\?\.isCommentAvailable == true" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift +rg -n "guard isCommentInteractionAvailable" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift +git diff --check +xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build +``` + +Expected: 단일 상태가 View·ViewModel에서 match, 상세 View의 기존 직접 조건 0건·exit code 1, mutation guard match, whitespace 오류 0건, `** BUILD SUCCEEDED **`. + +- [x] **수동 확인:** 타인 게시글의 유료 미구매 상태에서는 reaction·댓글 구분선·목록·입력창이 보이지 않고 댓글 요청이 발생하지 않는다. +- [x] **수동 확인:** 무료·구매 완료·본인 게시글에서는 기존 `isCommentAvailable` 값에 따라 댓글 UI와 동작이 유지된다. +- [x] **REFACTOR:** 기존 상세 응답·repository·답글 화면과 이미 완료된 reaction bar 구현은 변경하지 않는다. + +### Follow-up Task 2.6 유료 미구매 타인 게시글 더보기 숨김 + +**Goal 실행 `P2-R4`:** 홈·커뮤니티 탭 리스트 아이템의 더보기를 유료 미구매 타인 게시글에서 숨기고 무료·구매 완료·작성자 게시글에서 표시한다. + +- **시작 조건:** 사용자 상태별 수동 검증과 후속 노출 규칙 확정, PRD `CCCI-012`·`DEC-016` 반영. +- **완료 증거:** 공용 리스트 아이템 조건의 RED/GREEN contract check, `git diff --check`, Debug build, 상태별 수동 확인. +- **범위 밖:** grid·공지 카드, 메뉴 내용·권한, 가격 표시, 상세·좋아요 gesture 변경. + +**Files:** + +- Modify: `SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift` +- Test: 신규 test 파일 없음—focused contract check·Debug build·수동 확인 사용 + +**Interfaces:** + +- Consumes: 기존 `isPaidLocked`, `isOwnPost`, `post.price`, `post.existOrdered`. +- Produces: 유료 미구매 타인 게시글에는 더보기가 없고 나머지 상태에는 기존 더보기 callback이 유지되는 header. + +- [x] **RED:** 방문자 분기가 구매 상태와 무관하게 `else if !isOwnPost`로 더보기를 삽입함을 확인한다. +- [x] **GREEN:** 방문자 분기를 `else if !isPaidLocked`로 변경해 기존 잠금 판정을 재사용한다. +- [x] **GREEN 확인:** 기존 조건 0건, 신규 조건 1건, `git diff --check`, Debug build를 확인한다. +- [ ] **수동 확인:** 홈·커뮤니티 탭에서 유료 미구매 타인은 숨김, 무료·구매 완료·작성자는 표시된다. +- [x] **REFACTOR:** 신규 상태·helper 없이 조건 한 줄만 변경하고 grid·공지·메뉴 callback은 수정하지 않는다. + ### Phase 2 Gate **Goal 실행 `P2-GATE`:** 홈 재사용·상세·더보기·목록 동기화 구조를 판정한다. @@ -625,16 +700,18 @@ rg -n "CommunityPostCard\(" SodaLive/Sources/V2/CreatorChannel/Home/Components/C rg -n -U 'if !isPaidLocked \{\n[[:space:]]+reactionBar' SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift rg -n "onTapPurchase|onTapNotice|onTapComment" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift rg -n "onTapDetail|onTapCommunityDetail" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift +rg -n "isCommentInteractionAvailable|guard isCommentInteractionAvailable" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift +rg -n "else if !isPaidLocked" SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build ``` -Expected: whitespace 오류 0건, 필수 연결 match, 홈 `CommunityPostCard`·공지 기존 callback match 0건·exit code 1, `** BUILD SUCCEEDED **`. +Expected: whitespace 오류 0건, 필수 연결·상세 댓글 조건/guard match, 홈 `CommunityPostCard`·공지 기존 callback match 0건·exit code 1, `** BUILD SUCCEEDED **`. ## Phase 3: 리뷰·통합 검증·문서 종결 **Phase 결과:** 정적 계약·Debug build·Preview·시뮬레이터 흐름을 확인하고 PRD·계획·리뷰 기록을 동기화한다. -**선행조건:** `P2-GATE` 완료. 시뮬레이터 통합 판정은 `EXT-001` 반영 후 실행. +**선행조건:** `P2-R3`와 재개한 `P2-GATE` 완료. 시뮬레이터 통합 판정은 `EXT-001` 반영 후 실행. **Phase 완료 조건:** `P3-T1`, `P3-GATE` 완료·검증 기록 누적. @@ -657,7 +734,7 @@ Expected: whitespace 오류 0건, 필수 연결 match, 홈 `CommunityPostCard`· **Interfaces:** -- Consumes: `CCCI-001~008`, `EXT-001`, Phase 1·2 산출물. +- Consumes: `CCCI-001~012`, `EXT-001`, Phase 1·2 산출물. - Produces: Phase 3 리뷰 판정, 최종 Progress, 남은 외부 조건. - [x] 정적 검사로 필수 연결과 금지 호출처를 확인한다. @@ -685,6 +762,7 @@ Expected: exit code 0, `** BUILD SUCCEEDED **`. - [ ] Preview에서 홈 유료 잠금·고정·댓글 비활성 상태와 썸네일 유료 잠금 상태를 확인한다. - [ ] `EXT-001` 반영 후 시뮬레이터에서 홈·리스트형·썸네일형 본체 터치가 상세로 이동하고 목록 구매 dialog가 나오지 않음을 확인한다. - [ ] 본인 채널의 고정·수정·삭제, 타인 채널의 신고, 상세 구매, 더보기·좋아요 터치 중복 없음을 수동 확인한다. +- [ ] 상세의 유료 미구매 상태에서 reaction·댓글 UI 미노출과 댓글 mutation 차단을 확인하고, 구매 후 `isCommentAvailable`에 따른 복원을 확인한다. - [x] `docs/sample/sample-review.md`를 읽고 `phase3-community-item-unification.md`에 검토 범위·근거·명령·발견 사항을 기록한다. - [x] 확정 수정 항목이 있으면 코드 수정 전 해당 Phase에 회귀 Task를 추가한다. - [x] PRD 상태·요구사항 추적표와 이 문서의 체크박스·Progress를 실제 결과로 갱신한다. @@ -711,12 +789,14 @@ Expected: exit code 0, `** BUILD SUCCEEDED **`. | 6 | `P2-T2` | `P2-T1` | 아니요 | mutation 성공 callback 호출처 재확인 | | 7 | `P2-R1` | 사용자 회귀 제보·PRD 반영 | 아니요 | reaction bar 삽입 조건 재확인 | | 8 | `P2-R2` | 사용자 후속 요청·PRD 반영 | 아니요 | 공지 상세 callback 경계 재확인 | -| 9 | `P2-GATE` | Phase 2 Task 전체 | 아니요 | 실패 소유 Task 수정 | -| 10 | `P3-T1` | `P2-GATE` | 아니요 | 리뷰 수정 Task 추가 | -| 11 | `P3-GATE` | `P3-T1`·`EXT-001` | 아니요 | 백엔드 반영 후 통합 검증 재개 | +| 9 | `P2-R3` | 사용자 후속 요청·인터뷰·PRD 반영 | 아니요 | 상세 댓글 표시·mutation 진입점 재확인 | +| 10 | `P2-R4` | 사용자 상태별 수동 검증·PRD 반영 | 아니요 | 공용 리스트 아이템 더보기 조건 재확인 | +| 11 | `P2-GATE` | Phase 2 Task 전체 | 아니요 | 실패 소유 Task 수정 | +| 12 | `P3-T1` | `P2-GATE` | 아니요 | 리뷰 수정 Task 추가 | +| 13 | `P3-GATE` | `P3-T1`·`EXT-001` | 아니요 | 백엔드 반영 후 통합 검증 재개 | ```text -P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-R2 → P2-GATE → P3-T1 → P3-GATE +P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-R2 → P2-R3 → P2-R4 → P2-GATE → P3-T1 → P3-GATE ``` ## 변경 금지 항목 @@ -726,6 +806,7 @@ P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-R2 - 홈 API의 `dateUtc`를 임의로 rename·parse·timezone 변환하지 않는다. - 백엔드 미반영 필드에 optional·fallback 값을 만들어 통합 실패를 숨기지 않는다. - 신규 protocol·factory·display model·dependency·test target을 추가하지 않는다. +- 상세 API의 댓글 payload 계약과 `CreatorChannelReplyDetail/**`를 변경하지 않는다. - 요청 범위 밖의 기존 dead code·format·naming을 정리하지 않는다. ## 의사결정 및 중단 규칙 @@ -811,6 +892,53 @@ P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-R2 - 남은 항목: 기존 `EXT-001`·부팅된 Simulator 기반 통합 검증. - 다음 행동: 외부 조건 충족 후 `P3-GATE`를 재개한다. +### 상세 유료 미구매 댓글 차단 계획 — 2026-08-10 + +- 상태: `P2-R3` 계획 확정·구현 대기 +- 무엇을: 상세의 유료 미구매 상태에서 기존 reaction bar 차단을 유지하고 댓글 구분선·목록·입력창과 작성·수정·삭제 mutation을 차단하는 후속 Task를 추가했다. +- 왜: 현재 상세 View와 ViewModel은 `isCommentAvailable`만 확인해 유료 미구매 방문자도 댓글을 보고 mutation을 실행할 수 있기 때문이다. +- 어떻게: + - 사용자 인터뷰에서 UI와 ViewModel 동작을 모두 차단하는 A안을 확정했다. + - 기존 유료 잠금 조건과 댓글 허용 값을 결합한 단일 ViewModel 상태를 View 표시 조건·mutation guard에 재사용하도록 `P2-R3`에 기록했다. + - 이미 완료된 reaction bar 차단은 `P2-R1`을 유지하고 중복 Task를 만들지 않았다. +- 남은 항목: `P2-R3` 구현·focused contract check·Debug build·상태별 수동 확인. +- 다음 행동: 사용자가 별도로 구현을 요청할 때 `P2-R3`부터 실행한다. 현재 요청에 따라 코드 수정·커밋은 진행하지 않는다. + +### 상세 유료 미구매 댓글 차단 구현 — 2026-08-10 + +- 상태: `P2-R3` 코드·정적 계약·device/Simulator Debug build·상태별 수동 검증 완료 +- 무엇을: `isCommentInteractionAvailable` 하나로 상세 댓글 구분선·목록·입력창과 페이지네이션·작성·수정·삭제·상태 초기화를 통일했다. +- 왜: `isCommentAvailable`만으로는 타인 유료 미구매 게시글의 댓글 접근과 mutation을 차단하지 못하기 때문이다. +- 어떻게: + - RED: 상세 View 직접 조건 2건과 ViewModel의 paid-lock 미반영 guard를 확인했다. + - GREEN: 상세 View 직접 조건 0건, shared 상태 참조 10건, mutation guard match, `git diff --check` 성공을 확인했다. + - `SodaLive-dev` device Debug build와 iPhone 17 Pro iOS 26.0 Simulator Debug build가 각각 exit code 0으로 성공했다. + - Simulator에 앱을 설치·실행해 홈 진입을 캡처한 뒤 종료했다. +- 수동 검증: 사용자가 유료 미구매·무료·구매 완료·본인 게시글의 댓글 UI·요청 동작을 확인했다. +- 남은 항목: `EXT-001` 통합 검증. +- 다음 행동: 후속 `P2-R4`를 실행한다. + +### 유료 미구매 타인 게시글 더보기 숨김 계획 — 2026-08-10 + +- 상태: `P2-R4` 계획 확정·구현 대기 +- 무엇을: 홈·커뮤니티 탭의 공용 리스트 아이템에서 유료 미구매 타인의 더보기를 숨기고 무료·구매 완료·작성자에게 표시한다. +- 왜: 현재 방문자 분기 `else if !isOwnPost`가 구매 상태를 확인하지 않아 유료 미구매 게시글에도 더보기를 표시한다. +- 어떻게: 기존 `isPaidLocked`를 재사용해 방문자 분기를 `else if !isPaidLocked`로 교체하는 한 줄 변경과 focused RED/GREEN 검증을 `P2-R4`에 기록했다. +- 남은 항목: 코드 수정·정적 검사·Debug build·상태별 확인. +- 다음 행동: `P2-R4` RED 확인 후 최소 구현한다. + +### 유료 미구매 타인 게시글 더보기 숨김 구현 — 2026-08-10 + +- 상태: `P2-R4` 코드·정적 계약·Debug build 완료, 상태별 수동 확인 대기 +- 무엇을: 공용 `CreatorChannelCommunityListItem`의 방문자 더보기 조건을 `!isOwnPost`에서 `!isPaidLocked`로 변경했다. +- 왜: 홈·커뮤니티 리스트의 유료 미구매 타인 게시글에서 더보기를 제거하고 무료·구매 완료·작성자 게시글에는 유지하기 위해서다. +- 어떻게: + - RED: 기존 조건 1건·신규 조건 0건을 확인했다. + - GREEN: 기존 조건 0건·신규 조건 1건, production diff 한 줄, `git diff --check` 성공을 확인했다. + - `SodaLive-dev` Debug build가 exit code 0으로 성공했다. +- 남은 항목: 홈·커뮤니티 탭의 네 가지 상태별 수동 확인, `EXT-001` 통합 검증. +- 다음 행동: 상태별 수동 확인 후 `P2-R4`와 `P2-GATE`를 완료 판정한다. + ## Decision Log | 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | @@ -822,6 +950,8 @@ P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-R2 | 2026-08-10 | `PLAN-DEC-005` | 확정 | 목록의 기존 구매 상태·API 호출을 상세 View·ViewModel로 옮긴다. | 상세의 현재 `showPurchaseError()`만으로는 사용자 요구인 상세 구매가 불가능함 | `P1-T2`, `P1-T3` | | 2026-08-10 | `PLAN-DEC-006` | 확정 | 하트·댓글이 모두 숨겨지는 경우 높이 보정 대신 reaction bar 전체를 조건부로 제외한다. | 공용 리스트·상세의 빈 고정 높이를 같은 기존 상태로 제거하는 최소 변경 | `P2-R1`, `CCCI-009` | | 2026-08-10 | `PLAN-DEC-007` | 확정 | 공지 카드의 별도 구매·상세·댓글 callback을 단일 상세 callback으로 교체하고 기존 홈 상세 route를 재사용한다. | 공지도 커뮤니티 게시글이며 상세 전용 구매 원칙을 동일하게 적용 | `P2-R2`, `CCCI-010` | +| 2026-08-10 | `PLAN-DEC-008` | 확정 | 상세 View와 ViewModel은 기존 유료 잠금 조건과 `isCommentAvailable`을 결합한 단일 상태로 댓글 UI·작성·수정·삭제 mutation을 함께 차단한다. | 사용자 인터뷰 A안·PRD `DEC-015` | `P2-R3`, `CCCI-011` | +| 2026-08-10 | `PLAN-DEC-009` | 확정 | 공용 리스트 아이템의 방문자 더보기 분기를 기존 `isPaidLocked`의 반대로 제한한다. | 신규 helper 없이 홈·커뮤니티 리스트에 동시에 적용되는 최소 변경 | `P2-R4`, `CCCI-012` | ## 발견된 문제 @@ -831,14 +961,16 @@ P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-R2 | `ISSUE-002` | High | 환경 대기 | 사용 가능한 Simulator가 모두 `Shutdown`이라 실제 터치·메뉴·구매 검증을 실행하지 못했다. | `P3-T1`, `P3-GATE` | Simulator 부팅 후 수동 검증을 재개한다. | | `ISSUE-003` | Medium | 수정 완료 | 유료 잠금 상태에서 하트·댓글은 숨겨지지만 빈 reaction bar의 고정 24pt 높이가 홈·목록·상세에 남는다. | `P2-R1`, `P3-GATE` | 조건부 삽입 contract check·Debug build 통과. | | `ISSUE-004` | High | 수정 완료 | 홈 공지는 별도 구매 callback이 남고 실제 조립부가 상세 callback을 전달하지 않아 상세 전용 구매 원칙을 충족하지 않는다. | `P2-R2`, `P3-GATE` | 단일 상세 callback 연결·직접 구매 callback 제거·Debug build 통과. | +| `ISSUE-005` | High | 수정·수동 검증 완료 | 상세가 `isCommentAvailable`만으로 댓글 UI·전송 가능 여부를 판정해 유료 미구매 상태에서도 댓글을 조회하고 mutation을 실행할 수 있다. | `P2-R3`, `P2-GATE`, `P3-GATE` | 단일 댓글 상호작용 상태와 mutation guard를 적용하고 사용자가 상태별 동작을 확인했다. | +| `ISSUE-006` | Medium | 코드·자동 검증 완료·수동 대기 | 공용 리스트 아이템이 타인 게시글이면 구매 상태와 무관하게 더보기를 표시한다. | `P2-R4`, `P2-GATE` | 방문자 분기를 `!isPaidLocked`로 제한했다. 홈·커뮤니티 리스트 상태를 수동 검증한다. | ## 최종 보고 형식 ```markdown -구현 결과: 홈·커뮤니티 아이템 통일과 상세 구매 일원화 +구현 결과: 홈·커뮤니티 아이템 통일과 상세 구매·댓글 잠금 일원화 -- 변경: 홈 아이템 재사용, 목록 구매 제거, 더보기·상세·잠금 색상 통일 -- 결정: `DEC-001~012`, `PLAN-DEC-001~005` +- 변경: 홈 아이템 재사용, 목록 구매 제거, 더보기·상세·잠금 색상 통일, 상세 유료 미구매 댓글 차단 +- 결정: `DEC-001~015`, `PLAN-DEC-001~008` - 검증: - 정적 contract check·`git diff --check` — 실제 결과 - `SodaLive-dev` Debug build — 실제 결과 diff --git a/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md b/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md index a8713a29..47fefb9a 100644 --- a/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md +++ b/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md @@ -4,7 +4,7 @@ | 항목 | 내용 | |---|---| -| 문서 상태 | 공지 상세 구매 일원화·Debug build 완료·`EXT-001`/Simulator 통합 검증 대기 | +| 문서 상태 | `P2-R3` 상태별 수동 검증 완료·`P2-R4` 코드/자동 검증 완료·더보기 상태별 수동 확인 대기 | | 작성일 | 2026-08-10 | | 최종 수정일 | 2026-08-10 | | 대상 제품 | SodaLive iOS 크리에이터 채널 홈·커뮤니티 | @@ -15,7 +15,7 @@ ## 1. Overview -크리에이터 채널 홈 탭의 커뮤니티 요약 아이템을 커뮤니티 탭 리스트와 동일한 `CreatorChannelCommunityListItem`으로 표시한다. 홈의 커뮤니티·공지와 커뮤니티 탭의 아이템 터치는 게시글 상세로 이동하고, 유료 게시글 구매는 상세 화면에서만 시작한다. 유료 미구매 이미지 영역은 상세 화면의 `Color.gray800`과 동일하게 통일하고, 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar의 빈 높이도 제거한다. +크리에이터 채널 홈 탭의 커뮤니티 요약 아이템을 커뮤니티 탭 리스트와 동일한 `CreatorChannelCommunityListItem`으로 표시한다. 홈의 커뮤니티·공지와 커뮤니티 탭의 아이템 터치는 게시글 상세로 이동하고, 유료 게시글 구매는 상세 화면에서만 시작한다. 유료 미구매 이미지 영역은 상세 화면의 `Color.gray800`과 동일하게 통일하고, 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar의 빈 높이도 제거한다. 커뮤니티 상세의 유료 미구매 방문자에게는 댓글 영역을 표시하지 않고 댓글 작성·수정·삭제 동작을 차단한다. ## 2. Problem Statement @@ -25,6 +25,7 @@ - 현재 `CreatorChannelCommunityListItem`의 유료 미구매 영역은 목록에서 구매 다이얼로그를 여는 별도 `Button`이므로, 전체 아이템을 상세 진입으로 통일하려는 요구와 충돌한다. - 홈·커뮤니티 리스트와 상세는 하트·댓글을 모두 숨기는 유료 잠금 상태에서도 높이 24pt의 빈 reaction bar를 삽입해 불필요한 세로 공간이 남는다. - 홈 공지 카드는 상세·댓글·구매 callback이 기본 no-op이고, 유료 잠금 영역에 목록 구매용 `Button`이 남아 있어 상세 전용 구매 원칙에서 제외되어 있다. +- 커뮤니티 상세는 `isCommentAvailable`만으로 댓글 목록과 입력창을 표시해, 유료 미구매 잠금 상태에서도 댓글을 조회하고 작성·수정·삭제할 수 있다. ## 3. Goals @@ -36,6 +37,7 @@ - 홈 공지 게시글도 본체·댓글·유료 잠금 영역을 상세 진입으로 통일한다. - 유료 미구매 이미지 영역을 `Color.gray800`으로 통일한다. - 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar가 레이아웃 공간을 차지하지 않게 한다. +- 유료 미구매 방문자는 커뮤니티 상세에서 댓글 영역을 보거나 댓글 작성·수정·삭제 동작을 실행할 수 없게 한다. ### 3.2 UX 목표 @@ -43,6 +45,7 @@ - 아이템 본문·이미지·유료 잠금 영역을 터치하면 예상 가능하게 상세 화면으로 이동해야 한다. - 더보기·좋아요 같은 독립 제어의 터치는 아이템 상세 터치와 중복 실행되지 않아야 한다. - 표시할 reaction이 없으면 콘텐츠 아래에 빈 여백이 남지 않아야 한다. +- 유료 게시글 구매 전에는 상세의 댓글 구분선·목록·입력창이 보이지 않고, 구매 후에는 `isCommentAvailable` 정책에 따라 다시 표시되어야 한다. ## 4. Non-Goals @@ -50,19 +53,23 @@ - 썸네일형 커뮤니티 아이템의 레이아웃은 변경하지 않는다. - `CommunityPostCard`의 다른 사용처인 메인 홈 팔로잉·추천 섹션은 변경하지 않는다. - 신규 endpoint, 앱 dependency, 범위 밖 커뮤니티 리팩터링은 추가하지 않는다. +- 상세 API 응답의 댓글 payload 계약과 별도 답글 상세 화면은 변경하지 않는다. ## 5. Target Users and Permissions - 타인 채널 방문자: 더보기에서 신고 동작을 사용할 수 있다. - 본인 채널의 크리에이터: 기존 커뮤니티 게시글 관리 권한 범위 안에서 더보기 동작을 사용한다. - 유료 미구매 방문자: 목록에서 구매하지 않고 상세 화면에서 구매를 시작한다. +- 유료 미구매 방문자: 구매 전에는 상세의 reaction·댓글 영역을 볼 수 없고 댓글 mutation을 실행할 수 없다. ## 6. 핵심 사용자 흐름 1. 사용자가 크리에이터 채널 홈의 커뮤니티 요약 또는 공지 아이템을 확인한다. 2. 아이템 본체를 터치하면 해당 `postId`의 커뮤니티 게시글 상세로 이동한다. 3. 유료 미구매 게시글이면 상세 화면에서 기존 구매 다이얼로그와 API를 사용한다. -4. 사용자가 우측 상단 더보기를 터치하면 확정된 권한 규칙에 맞는 메뉴가 표시된다. +4. 구매 전에는 상세의 reaction·댓글 영역을 표시하지 않고 댓글 작성·수정·삭제 진입을 차단한다. +5. 구매 성공 후 상세를 다시 조회하고 `isCommentAvailable`에 따라 댓글 영역을 표시한다. +6. 사용자가 우측 상단 더보기를 터치하면 확정된 권한 규칙에 맞는 메뉴가 표시된다. ## 7. 기능 요구사항 @@ -78,6 +85,8 @@ | `CCCI-008` | 외부 의존 | 백엔드 홈 API의 `communities`·`notices` 아이템에 `isCommentAvailable: Bool`·`isPinned: Bool`을 커뮤니티 탭과 동일하게 제공한다. | iOS가 두 필드를 임의 기본값 없이 디코딩해 고정·댓글 상태를 표시한다. | `P1-T1`, `EXT-001` | | `CCCI-009` | 확정 | 홈·커뮤니티 리스트와 커뮤니티 상세에서 하트·댓글을 모두 표시하지 않을 때 reaction bar를 삽입하지 않는다. | 유료 미구매 잠금 상태에서 하트·댓글과 고정 24pt 높이가 모두 사라지고, 하나라도 표시되면 기존 reaction bar가 유지된다. | `P2-R1` | | `CCCI-010` | 확정 | 홈 공지 게시글도 목록 구매를 제거하고 본체·댓글·유료 잠금 영역을 상세 이동으로 처리한다. | 공지 카드에서 구매 다이얼로그가 직접 열리지 않고 세 영역 모두 선택한 `postId`의 상세로 이동한다. | `P2-R2` | +| `CCCI-011` | 확정 | 커뮤니티 상세의 유료 미구매 잠금 상태에서는 댓글 영역을 표시하지 않고 댓글 작성·수정·삭제 동작을 차단한다. | 댓글 구분선·제목·목록·하단 입력창이 View 계층에 없고 ViewModel의 댓글 mutation 진입점이 API 요청을 실행하지 않는다. 구매 후에는 `isCommentAvailable`에 따라 기존 댓글 UI와 동작이 복원된다. | `P2-R3` | +| `CCCI-012` | 확정 | 홈·커뮤니티 탭 리스트 아이템의 더보기는 유료 미구매 타인 게시글에서 숨기고, 무료·구매 완료 게시글과 작성자 본인 게시글에서 표시한다. | `CreatorChannelCommunityListItem`의 더보기가 유료 미구매 타인에게 없고 나머지 상태에는 표시된다. | `P2-R4` | ## 8. UI/UX Expectations @@ -87,6 +96,8 @@ - 리스트형 유료 미구매 영역은 236pt 높이를, 썸네일형은 기존 정사각형 비율을 유지한다. 두 형태 모두 잠금 icon·가격 capsule을 유지하고 배경만 `Color.gray800`으로 통일한다. - 더보기와 좋아요 제어는 자신의 action만 실행하고 상세 이동을 중복 실행하지 않는다. - 유료 미구매 잠금 상태처럼 하트·댓글을 모두 숨기는 경우 reaction bar 전체를 조건부로 제외해 고정 높이와 `VStack` 간격을 남기지 않는다. +- 커뮤니티 상세의 유료 미구매 잠금 상태에서는 본문 아래 댓글 구분선·댓글 제목과 목록·하단 입력창을 모두 삽입하지 않는다. +- 홈·커뮤니티 탭 리스트 아이템의 우측 상단 더보기는 유료 미구매 타인 게시글에서 레이아웃과 hit-testing 모두 제거하고, 작성자에게는 구매 여부와 무관하게 항상 표시한다. ## 9. 기술·운영 제약 @@ -96,6 +107,8 @@ - 백엔드는 홈 API 응답에 커뮤니티 탭과 동일한 `isCommentAvailable: Bool`·`isPinned: Bool`을 제공한다. iOS는 두 필드가 없는 응답을 추정 기본값으로 보정하지 않는다. - 홈 응답 모델과 커뮤니티 탭 응답 모델의 차이를 제거하기 위한 투기적 protocol·factory·신규 dependency를 추가하지 않는다. - 본인 게시글 판정은 기존처럼 `creatorId == UserDefaults.int(forKey: .userId)`를 사용한다. +- 유료 미구매 잠금 판정은 기존처럼 `price > 0 && existOrdered == false && isOwnPost == false`를 사용하며, 본인 게시글에는 댓글 차단을 적용하지 않는다. +- 더보기 노출도 같은 유료 미구매 잠금 판정을 재사용하고 별도 권한 상태를 추가하지 않는다. ## 10. 성공 기준 @@ -108,6 +121,9 @@ - [ ] 더보기·좋아요 터치가 상세 이동을 중복 발생시키지 않는다. - [x] 홈·커뮤니티 리스트와 상세의 유료 미구매 상태에서 하트·댓글용 빈 공간이 남지 않는다. - [ ] 홈 공지의 본체·댓글·유료 잠금 영역이 상세로 이동하고 공지 카드에서 구매 다이얼로그가 직접 열리지 않는다. +- [x] 커뮤니티 상세의 유료 미구매 상태에서 댓글 구분선·목록·입력창이 보이지 않고 작성·수정·삭제 API 요청이 실행되지 않는다. +- [x] 무료·구매 완료·본인 게시글은 기존 `isCommentAvailable` 값에 따라 댓글 영역과 동작을 유지한다. +- [ ] 홈·커뮤니티 탭 리스트 아이템의 더보기는 유료 미구매 타인 게시글에서 숨고, 무료·구매 완료·작성자 게시글에서 표시된다. - [x] `SodaLive-dev` 스킴 Debug 빌드가 성공한다. - [ ] 시뮬레이터에서 홈·커뮤니티 탭·상세의 아이템 터치, 더보기, 유료 잠금 색상을 확인한다. @@ -119,6 +135,7 @@ | `OQ-002` | 확정 | 목록 구매 제거를 홈과 커뮤니티 탭 모두에 적용한다. | A안 확정 | 사용자 | 2026-08-10 | `CCCI-004`, `CCCI-007` | | `OQ-003` | 확정 | 백엔드 홈 API 응답에 `isCommentAvailable`·`isPinned`을 커뮤니티 탭과 동일하게 추가한다. | A안 확정 | 사용자·API 담당 | iOS 통합 검증 전 | `CCCI-001`, `CCCI-008`, `EXT-001` | | `OQ-004` | 확정 | 홈 API의 날짜 키 `dateUtc`를 유지하고 iOS 로컬 변환에서 같은 UTC 문자열을 커뮤니티 탭 모델의 `createdAtUtc`로 연결한다. | A안 확정·날짜 값 파싱·시간대·포맷 변환 없음 | 사용자 | 2026-08-10 | `DEC-008` | +| `OQ-005` | 확정 | 유료 미구매 상태의 댓글 차단을 UI에만 적용할지 ViewModel mutation까지 방어할지 결정한다. | A안 확정·댓글 UI와 작성·수정·삭제 동작 모두 차단 | 사용자 | 2026-08-10 | `CCCI-011`, `DEC-015` | ## 12. 외부 제공 대기 계약 @@ -135,6 +152,7 @@ | `CCCI-008` | 홈 API 필드 확장 | `P1-T1`, `P3-GATE`, `EXT-001` | decoding contract·Debug build | 백엔드 반영 후 홈 응답 확인 | | `CCCI-009` | 기존 목록·상세 모델 재사용 | `P2-R1` | reaction bar 조건부 삽입 contract check·Debug build | 홈·커뮤니티 리스트·상세 유료 잠금 여백 확인 | | `CCCI-010` | 기존 홈 상세 route·상세 구매 API 재사용 | `P2-R2` | 공지 구매 callback 제거·상세 callback 연결 contract check·Debug build | 공지 본체·댓글·유료 잠금 상세 진입 확인 | +| `CCCI-011` | 기존 상세 응답·댓글 mutation API 재사용 | `P2-R3` | 댓글 표시 조건·mutation guard contract check·Debug build | 구매 전 댓글 UI 미노출·구매 후 복원 확인 | ## 14. Decision Log @@ -154,6 +172,8 @@ | 2026-08-10 | `DEC-012` | 확정 | 목록에 있던 기존 구매 다이얼로그·API 호출 소유권을 상세 화면으로 옮긴다. | 구현 전 기준선에서 상세 잠금 버튼이 구매가 아닌 `showPurchaseError()`만 호출함을 확인·사용자 직접 요구 충족 | `CCCI-004`, `CCCI-007` | | 2026-08-10 | `DEC-013` | 확정 | 하트·댓글이 모두 숨겨지는 유료 잠금 상태에서는 고정 높이를 보정하지 않고 reaction bar 전체를 조건부로 삽입하지 않는다. | 목록·상세의 공통 원인은 빈 `HStack`의 `.frame(height: 24)` 유지이며, 상위 조건부 삽입이 가장 작은 근본 수정이다. | `CCCI-009`, `P2-R1` | | 2026-08-10 | `DEC-014` | 확정 | 홈 공지도 별도 구매 callback을 제거하고 기존 `onTapCommunityDetail`·`showCommunityPostDetail(_:)` 흐름을 재사용한다. | 공지도 커뮤니티 게시글이며 구매는 상세에서만 가능해야 한다는 사용자 요청 | `CCCI-010`, `P2-R2` | +| 2026-08-10 | `DEC-015` | 확정 | 상세의 유료 미구매 잠금 상태에서는 기존 reaction bar 차단을 유지하고, 댓글 구분선·목록·입력창과 ViewModel의 작성·수정·삭제 mutation 진입을 모두 차단한다. | 사용자 후속 요청과 인터뷰 A안 선택 | `CCCI-009`, `CCCI-011`, `P2-R1`, `P2-R3` | +| 2026-08-10 | `DEC-016` | 확정 | 홈·커뮤니티 탭의 공용 리스트 아이템은 기존 `isPaidLocked`를 재사용해 더보기를 조건부 삽입한다. 작성자는 구매 여부와 무관하게 표시한다. | 사용자 상태별 수동 검증 후속 요청·공용 컴포넌트와 기존 잠금 판정 재사용 | `CCCI-012`, `P2-R4` | ## 15. 구현 설계 @@ -173,7 +193,10 @@ - 상세 진입 helper는 표시 모델 전체 대신 `postId` 만 받아 홈·리스트형·썸네일형이 같은 route를 사용한다. - 더보기·좋아요 `Button`은 각자의 action만 실행하고 부모 아이템의 상세 진입을 중복 실행하지 않아야 한다. - `CreatorChannelCommunityListItem`과 `CreatorChannelCommunityPostDetailContentView`는 `isPaidLocked`가 `true`이면 `reactionBar` 자체를 View 계층에 삽입하지 않는다. 댓글만 비활성이고 좋아요가 표시되는 상태는 기존 reaction bar를 유지한다. +- `CreatorChannelCommunityPostDetailViewModel`은 상세의 `isCommentAvailable`과 기존 유료 잠금 조건을 함께 판정하는 단일 댓글 상호작용 상태를 제공한다. 상세 View는 이 상태가 `true`일 때만 댓글 구분선·목록·하단 입력창을 삽입한다. +- 같은 상태를 댓글 전송 가능 여부와 작성·수정·삭제 mutation 진입 guard에 재사용한다. 잠금 상태에서는 댓글 API 요청을 시작하지 않는다. - `CreatorChannelNoticeSection`은 본체·댓글·유료 잠금 영역에 하나의 상세 callback을 사용하고, `CreatorChannelHomeView`의 기존 `onTapCommunityDetail`을 전달한다. 공지 카드의 외형은 변경하지 않는다. +- `CreatorChannelCommunityListItem.header`의 방문자 더보기 분기를 `!isPaidLocked`로 제한한다. 기존 작성자 제어 분기·가격 표시·callback은 유지한다. ### 15.3 더보기와 변경 후 갱신 @@ -197,6 +220,8 @@ - 정적 검증은 `rg`로 목록 `onTapPurchase`·목록 구매 dialog 호출처 제거, 상세 구매 dialog·API 연결, `Color.gray800`, 홈·목록·썸네일 상세 route 연결을 확인하고 `git diff --check`를 실행한다. - reaction bar 회귀 검증은 두 대상 View의 body에 무조건 삽입된 `reactionBar`가 없고 `if !isPaidLocked` 내부에만 삽입되는지 focused `rg`로 확인한다. - 공지 회귀 검증은 `CreatorChannelNoticeSection`의 `onTapPurchase`·`onTapNotice`·`onTapComment`가 제거되고 단일 상세 callback이 `CreatorChannelHomeView.onTapCommunityDetail`에 연결되는지 focused `rg`로 확인한다. +- 상세 댓글 차단 검증은 `CreatorChannelCommunityPostDetailView`가 ViewModel의 단일 댓글 상호작용 상태로 댓글 구분선·목록·입력창을 조건부 삽입하고, `CreatorChannelCommunityPostDetailViewModel`의 전송·삭제 mutation 진입점이 같은 상태를 guard하는지 focused `rg`로 확인한다. +- 더보기 노출 검증은 공용 리스트 아이템의 기존 `else if !isOwnPost`를 RED로 확인하고 `else if !isPaidLocked`로 변경된 것을 GREEN으로 확인한다. - 자동 검증은 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`로 컴파일·링크 성공을 확인한다. - `EXT-001` 반영 후 시뮬레이터에서 홈·리스트형·썸네일형의 상세 진입, 더보기 권한 메뉴, 목록 구매 dialog 미노출, 상세 구매, `Color.gray800`을 수동 확인한다. @@ -216,3 +241,10 @@ - 2026-08-10: 두 body의 무조건 `reactionBar` 삽입을 `if !isPaidLocked` 조건부 삽입으로 변경했다. focused `rg`에서 무조건 삽입 0건·조건부 삽입 2건, `git diff --check` 오류 0건, 동일 iPhone 17 Pro 대상 `SodaLive-dev` Debug build exit code 0을 확인했다. - 2026-08-10: `CreatorChannelNoticeSection`에 기본 no-op인 상세·댓글 callback과 별도 구매 callback·잠금 `Button`이 남고 `CreatorChannelHomeView`가 상세 callback을 전달하지 않는 `REV-P3-004`를 확인해 `CCCI-010`, `DEC-014`, `P2-R2`로 수정 범위를 확정했다. - 2026-08-10: 공지 callback을 단일 `onTapDetail`로 통일하고 잠금 영역의 직접 구매 `Button`을 제거한 뒤 `CreatorChannelHomeView.onTapCommunityDetail`에 연결했다. RED 계약 검사는 직접 구매 callback과 미연결 생성부를 각각 검출했고, GREEN은 기존 callback 0건·상세 연결 match, `git diff --check` 오류 0건, `SodaLive-dev` Debug build exit code 0을 확인했다. +- 2026-08-10: 사용자 후속 요청과 인터뷰 A안 선택으로 커뮤니티 상세의 유료 미구매 상태에서 reaction bar 차단을 유지하고 댓글 UI·작성·수정·삭제 mutation을 모두 차단하기로 확정했다. 현재 상세 View와 ViewModel이 `isCommentAvailable`만으로 댓글 표시·전송을 허용하는 기준선을 확인했으며 구현·커밋은 진행하지 않았다. +- 2026-08-10: `P2-R3` RED에서 상세 View의 `isCommentAvailable` 직접 조건 2건과 ViewModel의 paid-lock 미반영 상태를 확인했다. `isCommentInteractionAvailable` 하나로 댓글 구분선·목록·입력창과 페이지네이션·전송·편집·삭제·상태 초기화를 통일했다. +- 2026-08-10: GREEN focused contract check에서 상세 View 직접 조건 0건, shared 상태 참조 10건, mutation guard match를 확인했고 `git diff --check`가 통과했다. device와 iPhone 17 Pro iOS 26.0 Simulator 대상 `SodaLive-dev` Debug build가 각각 exit code 0으로 성공했다. +- 2026-08-10: iPhone 17 Pro Simulator에 앱을 설치·실행해 홈 진입을 확인하고 `/tmp/p2-r3-launch.png`를 캡처했다. 익명 세션에 유효한 유료 커뮤니티 상세 데이터가 없어 `CCCI-011` 상태별 화면·요청 검증은 완료하지 못했으며 Simulator는 종료했다. +- 2026-08-10: 사용자가 유료 미구매·무료·구매 완료·본인 게시글의 상세 댓글 상태를 수동 확인해 `CCCI-011`의 두 성공 기준을 완료 처리했다. +- 2026-08-10: 후속 요청으로 홈·커뮤니티 탭 리스트 아이템의 더보기를 유료 미구매 타인에게 숨기고 무료·구매 완료·작성자에게 표시하는 `CCCI-012`, `DEC-016`, `P2-R4`를 확정했다. +- 2026-08-10: `P2-R4` RED에서 방문자 분기의 `else if !isOwnPost` 1건을 확인하고 `else if !isPaidLocked`로 교체했다. GREEN에서 기존 조건 0건·신규 조건 1건, production diff 한 줄, `git diff --check` 성공과 `SodaLive-dev` Debug build exit code 0을 확인했다. diff --git a/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/reviews/phase3-community-item-unification.md b/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/reviews/phase3-community-item-unification.md index ac759477..84700480 100644 --- a/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/reviews/phase3-community-item-unification.md +++ b/docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/reviews/phase3-community-item-unification.md @@ -4,18 +4,18 @@ | 항목 | 내용 | |---|---| -| 리뷰 대상 | Phase 1~2 / `P1-T1~P2-R2` | +| 리뷰 대상 | Phase 1~2 / `P1-T1~P2-R4` | | 기준 commit 또는 working tree | `89c070875cbeea8395c1e90cb9f47ff80df5d90f` 이후 미커밋 변경 | | 리뷰 일자 | 2026-08-10 | -| 리뷰어 | Codex 자가 리뷰—프로젝트 보수적 실행 정책에 따라 subagent 미사용 | +| 리뷰어 | Sisyphus 구현 검토·Oracle 독립 검토 | | 기준 문서 | `docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md`, `plan-task.md` | -| 리뷰 상태 | `REV-P3-004` 수정 완료·외부 통합 검증 대기 | +| 리뷰 상태 | `REV-P3-005` 수동 검증 완료·`REV-P3-006` 코드/자동 검증 완료·상태별 더보기 확인 대기 | ## 2. 리뷰 목적과 범위 ### 목적 -- 홈·커뮤니티 탭의 아이템과 공지, 상세 진입, 더보기, 목록 구매 제거, 상세 구매 이동이 `CCCI-001~010`을 충족하는지 확인한다. +- 홈·커뮤니티 탭의 아이템과 공지, 상세 진입, 더보기 노출, 목록 구매 제거, 상세 구매 이동·댓글 잠금이 `CCCI-001~012`를 충족하는지 확인한다. - 코드·문서·검증 기록이 실제 working tree와 일치하는지 확인한다. ### 포함 범위 @@ -50,8 +50,8 @@ ### 문서와 코드 -- 요구사항: `CCCI-001~010`, `DEC-001~014`, `EXT-001` -- 계획: `P1-T1~P2-T2`, `P2-R1`, `P2-R2`, `P1-GATE`, `P2-GATE` +- 요구사항: `CCCI-001~012`, `DEC-001~016`, `EXT-001` +- 계획: `P1-T1~P2-T2`, `P2-R1`, `P2-R2`, `P2-R3`, `P2-R4`, `P1-GATE`, `P2-GATE` - 홈 변환·아이템: `CreatorChannelCommunitySection.swift:3` - 목록 잠금·상세 gesture: `CreatorChannelCommunityListItem.swift`, `CreatorChannelCommunityGridItem.swift` - 상세 구매: `CreatorChannelCommunityPostDetailView.swift:19`, `CreatorChannelCommunityPostDetailViewModel.swift:186` @@ -76,9 +76,13 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함 | 상세 구매 `rg` contract check | 성공 | `showPurchaseError` 0건, 상세 dialog·repository 호출 match | | reaction bar 조건부 삽입 `rg` contract check | 성공 | 무조건 삽입 0건, `if !isPaidLocked` 조건부 삽입 2건 | | 공지 상세 전용 구매 `rg` RED/GREEN contract check | 성공 | RED 직접 구매 callback 7건·미연결 생성부 1건, GREEN 기존 callback 0건·상세 연결 match | +| 상세 댓글 잠금 `rg` RED/GREEN contract check | 성공 | RED View 직접 조건 2건, GREEN 직접 조건 0건·shared 상태 참조 10건·mutation guard match | +| 리스트 더보기 노출 `rg` RED/GREEN contract check | 성공 | RED `else if !isOwnPost` 1건, GREEN 기존 조건 0건·`else if !isPaidLocked` 1건·production diff 한 줄 | +| P2-R4 Oracle 독립 코드 리뷰 | 승인 | 네 상태 논리표·공용 호출부·범위 외 회귀·문서 추적성 확인, blocker 0건 | +| Oracle 독립 코드 리뷰 | 승인 | 세 상태 시나리오·mutation API 경로·문서 보류 판정 확인, blocker 0건 | | `git diff --check` | 성공 | whitespace 오류 0건 | | `xcodebuild -quiet -workspace SodaLive.xcworkspace -scheme SodaLive-dev -configuration Debug -destination 'platform=iOS Simulator,id=E9FC7721-AA96-440F-8349-2DC4B85F40F5' -derivedDataPath /tmp/sodalive-community-baseline CODE_SIGNING_ALLOWED=NO ONLY_ACTIVE_ARCH=YES build` | 성공 | exit code 0 | -| `xcrun simctl list devices` | 수동 검증 불가 | 모든 사용 가능한 Simulator가 `Shutdown`; 스킬 정책상 임의 부팅하지 않음 | +| iPhone 17 Pro iOS 26.0 Simulator build·install·launch·screenshot | 부분 성공 | Simulator build exit 0, 홈 실행 `/tmp/p2-r3-launch.png`; 익명 세션에 유효 상세 데이터가 없어 상태별 상세 검증 대기, 이후 Simulator 종료 | ## 5. 발견 사항 요약 @@ -90,6 +94,8 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함 | `REV-P3-002` | High | 보류 | 홈 API 신규 필드와 런타임 동작의 통합 검증 필요 | `P3-GATE`, `EXT-001` | 외부 반영 후 재개 | | `REV-P3-003` | Medium | 수정 완료 | 하트·댓글이 숨겨져도 빈 reaction bar가 24pt 공간을 차지함 | `P2-R1` | 완료 | | `REV-P3-004` | High | 수정 완료 | 홈 공지에 목록 구매 callback이 남고 상세 callback이 연결되지 않음 | `P2-R2` | 완료 | +| `REV-P3-005` | High | 수정·수동 검증 완료 | 유료 미구매 상세가 댓글 UI와 mutation을 허용함 | `P2-R3` | 완료 | +| `REV-P3-006` | Medium | 코드·자동 검증 완료·수동 대기 | 유료 미구매 타인 게시글에도 더보기가 표시됨 | `P2-R4` | 홈·커뮤니티 상태별 확인 | ## 6. 발견 사항 상세 @@ -129,11 +135,31 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함 `CreatorChannelNoticeSection`은 `onTapNotice`·`onTapComment`·`onTapPurchase`를 각각 기본 no-op으로 선언하고 잠금 영역에 별도 구매 `Button`을 뒀다. `CreatorChannelHomeView`는 notices만 전달해 상세 callback을 연결하지 않았다. 코드 수정 전에 `P2-R2`를 추가하고, 세 callback을 단일 `onTapDetail`로 통일해 기존 `onTapCommunityDetail` route에 연결했다. 잠금 구매 `Button` wrapper만 제거했으며 카드 외형과 상세 구매·refresh 구현은 변경하지 않았다. focused RED/GREEN contract check·`git diff --check`·Debug build가 통과했다. +### REV-P3-005 — 유료 미구매 상세의 댓글 접근 허용 + +- **심각도:** High +- **상태:** 수정·수동 검증 완료 +- **관련 요구사항:** `CCCI-011` +- **소유 Task:** `P2-R3` + +상세 View와 ViewModel이 `isCommentAvailable`만 확인해 타인 유료 미구매 게시글에서도 댓글 구분선·목록·입력창과 작성·수정·삭제 mutation을 허용했다. 기존 유료 잠금 조건과 `isCommentAvailable`을 결합한 `isCommentInteractionAvailable`을 추가하고 UI 삽입·페이지네이션·전송·편집·삭제·`applyDetail` 초기화에 재사용했다. 정적 contract check·device/Simulator Debug build가 통과했고 사용자가 상태별 상세 동작을 수동 확인했다. + +### REV-P3-006 — 유료 미구매 타인 게시글의 더보기 노출 + +- **심각도:** Medium +- **상태:** 코드·자동 검증 완료·수동 대기 +- **관련 요구사항:** `CCCI-012` +- **소유 Task:** `P2-R4` + +공용 리스트 아이템의 방문자 분기가 `else if !isOwnPost`만 확인해 유료 미구매 타인 게시글에도 더보기를 표시했다. 기존 `isPaidLocked`의 반대 조건으로 분기를 교체해 홈·커뮤니티 리스트에 함께 적용했다. production diff는 한 줄이며 정적 contract check·`git diff --check`·Debug build가 통과했다. + ## 7. 확정 항목의 plan·goal 전환 - `REV-P3-001`: 코드 수정 전에 `P1-T3`로 전환했고 수정·빌드를 완료했다. - `REV-P3-003`: 코드 수정 전에 `P2-R1`로 전환했고 수정·검증을 완료했다. - `REV-P3-004`: 코드 수정 전에 `P2-R2`로 전환했고 수정·검증을 완료했다. +- `REV-P3-005`: 코드 수정 전에 `P2-R3`로 전환했고 코드·자동·상태별 수동 검증을 완료했다. +- `REV-P3-006`: 코드 수정 전에 `P2-R4`로 전환했고 코드·자동 검증을 완료했다. 상태별 더보기 수동 확인은 대기한다. - `REV-P3-002`: 외부 조건이므로 `P3-GATE` 완료를 보류한다. ## 8. 리뷰 종료 판정 @@ -146,9 +172,9 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함 | 보류 항목의 담당·재개 조건 기록 | 충족 | `EXT-001`·부팅된 Simulator | | 검증 명령과 결과 기록 | 충족 | 정적 계약·Debug build exit 0 | -**최종 결론:** 홈 공지 상세 구매 일원화 수정·빌드 완료, 외부 통합 검증 대기 +**최종 결론:** 상세 댓글 차단 상태별 검증 완료, 더보기 노출 조건 코드·자동 검증 완료·상태별 수동 확인 대기 -**남은 항목:** 백엔드 `EXT-001` 반영 후 부팅된 Simulator에서 본체 상세 이동, 권한별 더보기, 목록 dialog 미노출, 상세 구매, `Color.gray800`을 확인한다. +**남은 항목:** 홈·커뮤니티 리스트에서 더보기 네 상태를 수동 확인하고, 백엔드 `EXT-001` 반영 후 나머지 통합 흐름을 확인한다. ## 9. 수정 후 검증 기록 @@ -183,3 +209,28 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함 - `git diff --check` — 성공 - `SodaLive-dev` Debug build — exit code 0 - 남은 항목: `REV-P3-002`와 Simulator 통합 검증 + +### 4차 수정 검증 — 2026-08-10 + +- 무엇을: `REV-P3-005`의 유료 미구매 상세 댓글 UI·mutation 허용을 차단했다. +- 왜: 구매 전 방문자는 reaction뿐 아니라 댓글 영역과 작성·수정·삭제 API에도 접근할 수 없어야 한다. +- 어떻게: + - RED: 상세 View의 `isCommentAvailable` 직접 조건 2건과 paid-lock 미반영 ViewModel guard 확인 + - GREEN: View 직접 조건 0건, shared 상태 참조 10건, mutation guard match + - `git diff --check` — 성공 + - device와 iPhone 17 Pro Simulator 대상 `SodaLive-dev` Debug build — 각각 exit code 0 + - Simulator build·install·launch·홈 캡처 후 종료 +- 남은 항목: 익명 세션에서 확보하지 못한 유효 유료 미구매·구매 완료·본인 게시글 기반 수동 검증, `REV-P3-002` +- 독립 리뷰: Oracle `APPROVE`, blocker 0건. 상태별 상세 fixture 부재는 문서화된 잔여 수동 검증 위험으로 판정했다. + +### 5차 수정 검증 — 2026-08-10 + +- 무엇을: `REV-P3-006`의 유료 미구매 타인 게시글 더보기 노출을 차단했다. +- 왜: 구매 전 타인 게시글은 관리·신고 더보기 진입점을 표시하지 않아야 한다. +- 어떻게: + - RED: `else if !isOwnPost` 1건 확인 + - GREEN: 기존 조건 0건, `else if !isPaidLocked` 1건, production diff 한 줄 + - `git diff --check` — 성공 + - `SodaLive-dev` Debug build — exit code 0 +- 남은 항목: 홈·커뮤니티 탭에서 유료 미구매 타인 숨김, 무료·구매 완료·작성자 표시 수동 확인 +- 독립 리뷰: 최초 문서 상태·추적 범위 2건을 수정한 뒤 Oracle 재검토 `APPROVE`, blocker 0건.