docs(creator): 커뮤니티 잠금 기록을 갱신한다

This commit is contained in:
Yu Sung
2026-08-10 18:44:14 +09:00
parent 8cbac16382
commit c8636a22f1
3 changed files with 247 additions and 32 deletions

View File

@@ -2,9 +2,9 @@
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:executing-plans` to implement this plan task-by-task. 프로젝트 보수적 실행 모드에 따라 multi-agent는 사용하지 않는다. > **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`. **Tech Stack:** Swift, SwiftUI, Combine, Moya, Kingfisher, SDWebImageSwiftUI, Xcode workspace `SodaLive.xcworkspace`.
@@ -17,6 +17,7 @@
- 목록의 리스트형·썸네일형 구매 호출은 제거하고 기존 구매 dialog·API 호출을 상세로 옮긴다. - 목록의 리스트형·썸네일형 구매 호출은 제거하고 기존 구매 dialog·API 호출을 상세로 옮긴다.
- 유료 미구매 영역은 홈·리스트형·썸네일형·상세 모두 `Color.gray800`을 사용한다. - 유료 미구매 영역은 홈·리스트형·썸네일형·상세 모두 `Color.gray800`을 사용한다.
- 하트·댓글을 모두 숨기는 유료 미구매 상태에서는 reaction bar의 고정 높이와 부모 spacing을 남기지 않는다. - 하트·댓글을 모두 숨기는 유료 미구매 상태에서는 reaction bar의 고정 높이와 부모 spacing을 남기지 않는다.
- 상세의 유료 미구매 상태에서는 댓글 구분선·목록·입력창을 표시하지 않고 작성·수정·삭제 API 요청을 시작하지 않는다.
- 현재 실행 가능한 단일 test bundle이 없으므로 신규 test 타겟을 만들지 않고 정적 contract check·Preview·Debug build·시뮬레이터로 검증한다. - 현재 실행 가능한 단일 test bundle이 없으므로 신규 test 타겟을 만들지 않고 정적 contract check·Preview·Debug build·시뮬레이터로 검증한다.
- 사용자가 직접 요청하지 않았으므로 `git commit`은 실행하지 않는다. - 사용자가 직접 요청하지 않았으므로 `git commit`은 실행하지 않는다.
@@ -24,24 +25,24 @@
| 문서 항목 | 내용 | | 문서 항목 | 내용 |
|---|---| |---|---|
| 상태 | reaction bar 회귀 수정·빌드 완료·외부 통합 검증 대기 | | 상태 | `P2-R3` 상태별 수동 검증 완료·`P2-R4` 코드/자동 검증 완료·상태별 수동 확인 대기 |
| 작성일 | 2026-08-10 | | 작성일 | 2026-08-10 |
| 요구사항 기준 | `docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md` | | 요구사항 기준 | `docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md` |
| API 기준 | `GET /api/v2/creator-channels/{creatorId}/home` 필드 확장·기존 커뮤니티 목록/상세 API | | API 기준 | `GET /api/v2/creator-channels/{creatorId}/home` 필드 확장·기존 커뮤니티 목록/상세 API |
| 현재 Phase | Phase 3 Gate | | 현재 Phase | Phase 2 |
| 현재 활성 Goal | `P3-T1`·`P3-GATE``EXT-001`·부팅된 Simulator 대기 | | 현재 활성 Goal | `P2-R4`—더보기 상태별 수동 확인 |
## 목표 ## 목표
홈 요약·커뮤니티 리스트·썸네일에서 게시글 터치 결과와 잠금 스타일을 통일하고, 더보기 권한 메뉴와 상세 구매 흐름을 정확하게 유지한다. 홈 요약·커뮤니티 리스트·썸네일에서 게시글 터치 결과와 잠금 스타일을 통일하고, 더보기 권한 메뉴와 상세 구매 흐름을 정확하게 유지한다. 상세의 유료 미구매 방문자에게는 reaction·댓글 영역을 표시하지 않고 댓글 mutation을 허용하지 않는다.
## 현재 상태 ## 현재 상태
| Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 | | Phase | 상태 | 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|---:|---|---:|---|---| |---:|---|---:|---|---|
| 1 | 완료 | `3/3` | `P1-GATE` 완료 | 없음 | | 1 | 완료 | `3/3` | `P1-GATE` 완료 | 없음 |
| 2 | 완료 | `3/3` | `P2-GATE` 완료 | 없음 | | 2 | 진행 | `5/6` | `P2-R4` | 상태별 수동 확인 |
| 3 | 진행 | `0/1` | `P3-T1`, `P3-GATE` | `EXT-001` 반영·Simulator 부팅 | | 3 | 진행 | `0/1` | `P3-T1`, `P3-GATE` | `EXT-001` 반영 |
## 범위 ## 범위
@@ -52,6 +53,7 @@
- 홈 섹션의 `CreatorChannelCommunityListItem` 재사용 - 홈 섹션의 `CreatorChannelCommunityListItem` 재사용
- 홈의 고정·수정·삭제·신고 메뉴 연결과 성공 후 목록 동기화 - 홈의 고정·수정·삭제·신고 메뉴 연결과 성공 후 목록 동기화
- 홈·커뮤니티 리스트와 상세에서 표시할 reaction이 없을 때 빈 공간 제거 - 홈·커뮤니티 리스트와 상세에서 표시할 reaction이 없을 때 빈 공간 제거
- 커뮤니티 상세의 유료 미구매 상태에서 댓글 구분선·목록·입력창 미표시와 작성·수정·삭제 mutation 차단
- 정적 검사·Preview·Debug build·시뮬레이터 검증·문서 누적 - 정적 검사·Preview·Debug build·시뮬레이터 검증·문서 누적
### 제외 ### 제외
@@ -61,6 +63,7 @@
- 커뮤니티 작성·상세·댓글의 범위 밖 리팩터링 - 커뮤니티 작성·상세·댓글의 범위 밖 리팩터링
- 신규 공용 모델·protocol·factory·dependency·test target 추가 - 신규 공용 모델·protocol·factory·dependency·test target 추가
- 백엔드 구현과 배포 - 백엔드 구현과 배포
- 상세 API 댓글 payload 계약과 `CreatorChannelReplyDetail/**` 변경
## 기술적 제약 ## 기술적 제약
@@ -69,6 +72,7 @@
- 홈·커뮤니티 변경 성공 후 갱신은 `CreatorChannelView`의 한 helper에서 두 ViewModel 재조회를 호출한다. - 홈·커뮤니티 변경 성공 후 갱신은 `CreatorChannelView`의 한 helper에서 두 ViewModel 재조회를 호출한다.
- 사용자 노출 오류는 기존 `errorMessage`·`isShowPopup`·toast 흐름을 유지한다. - 사용자 노출 오류는 기존 `errorMessage`·`isShowPopup`·toast 흐름을 유지한다.
- 홈 응답에 추가 필드가 없으면 decoding 실패를 기존 홈 API 실패로 처리하고 fallback 값을 만들지 않는다. - 홈 응답에 추가 필드가 없으면 decoding 실패를 기존 홈 API 실패로 처리하고 fallback 값을 만들지 않는다.
- 댓글 차단은 기존 유료 잠금 조건 `price > 0 && existOrdered == false && isOwnPost == false`와 상세의 `isCommentAvailable`을 함께 사용한다.
## Task 검증 규칙 ## Task 검증 규칙
@@ -335,11 +339,11 @@ Expected: whitespace 오류 0건, 필드 2건, 세 UI의 `Color.gray800`·상세
## Phase 2: 홈 아이템 재사용과 메뉴 연결 ## Phase 2: 홈 아이템 재사용과 메뉴 연결
**Phase 결과:** 홈이 기존 커뮤니티 리스트 아이템·상세 route·권한 메뉴를 재사용하고 변경 성공 후 홈·커뮤니티 목록을 동기화한다. **Phase 결과:** 홈이 기존 커뮤니티 리스트 아이템·상세 route·권한 메뉴를 재사용하고 변경 성공 후 홈·커뮤니티 목록을 동기화하며, 상세 유료 미구매 상태의 reaction·댓글 UI와 댓글 mutation을 차단한다.
**선행조건:** `P1-GATE` 완료. **선행조건:** `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 홈 섹션에 기존 리스트 아이템·상세·더보기 연결 ### Task 2.1 홈 섹션에 기존 리스트 아이템·상세·더보기 연결
@@ -614,6 +618,77 @@ Expected: 첫 명령 match 0건·exit code 1, 둘째 명령 필수 연결 match,
- [x] **REFACTOR:** 공지 카드 외형·오디오 재생과 기존 상세·구매·refresh 구현은 변경하지 않는다. - [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 ### Phase 2 Gate
**Goal 실행 `P2-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 -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 "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 "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 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 3: 리뷰·통합 검증·문서 종결
**Phase 결과:** 정적 계약·Debug build·Preview·시뮬레이터 흐름을 확인하고 PRD·계획·리뷰 기록을 동기화한다. **Phase 결과:** 정적 계약·Debug build·Preview·시뮬레이터 흐름을 확인하고 PRD·계획·리뷰 기록을 동기화한다.
**선행조건:** `P2-GATE` 완료. 시뮬레이터 통합 판정은 `EXT-001` 반영 후 실행. **선행조건:** `P2-R3`와 재개한 `P2-GATE` 완료. 시뮬레이터 통합 판정은 `EXT-001` 반영 후 실행.
**Phase 완료 조건:** `P3-T1`, `P3-GATE` 완료·검증 기록 누적. **Phase 완료 조건:** `P3-T1`, `P3-GATE` 완료·검증 기록 누적.
@@ -657,7 +734,7 @@ Expected: whitespace 오류 0건, 필수 연결 match, 홈 `CommunityPostCard`·
**Interfaces:** **Interfaces:**
- Consumes: `CCCI-001~008`, `EXT-001`, Phase 1·2 산출물. - Consumes: `CCCI-001~012`, `EXT-001`, Phase 1·2 산출물.
- Produces: Phase 3 리뷰 판정, 최종 Progress, 남은 외부 조건. - Produces: Phase 3 리뷰 판정, 최종 Progress, 남은 외부 조건.
- [x] 정적 검사로 필수 연결과 금지 호출처를 확인한다. - [x] 정적 검사로 필수 연결과 금지 호출처를 확인한다.
@@ -685,6 +762,7 @@ Expected: exit code 0, `** BUILD SUCCEEDED **`.
- [ ] Preview에서 홈 유료 잠금·고정·댓글 비활성 상태와 썸네일 유료 잠금 상태를 확인한다. - [ ] Preview에서 홈 유료 잠금·고정·댓글 비활성 상태와 썸네일 유료 잠금 상태를 확인한다.
- [ ] `EXT-001` 반영 후 시뮬레이터에서 홈·리스트형·썸네일형 본체 터치가 상세로 이동하고 목록 구매 dialog가 나오지 않음을 확인한다. - [ ] `EXT-001` 반영 후 시뮬레이터에서 홈·리스트형·썸네일형 본체 터치가 상세로 이동하고 목록 구매 dialog가 나오지 않음을 확인한다.
- [ ] 본인 채널의 고정·수정·삭제, 타인 채널의 신고, 상세 구매, 더보기·좋아요 터치 중복 없음을 수동 확인한다. - [ ] 본인 채널의 고정·수정·삭제, 타인 채널의 신고, 상세 구매, 더보기·좋아요 터치 중복 없음을 수동 확인한다.
- [ ] 상세의 유료 미구매 상태에서 reaction·댓글 UI 미노출과 댓글 mutation 차단을 확인하고, 구매 후 `isCommentAvailable`에 따른 복원을 확인한다.
- [x] `docs/sample/sample-review.md`를 읽고 `phase3-community-item-unification.md`에 검토 범위·근거·명령·발견 사항을 기록한다. - [x] `docs/sample/sample-review.md`를 읽고 `phase3-community-item-unification.md`에 검토 범위·근거·명령·발견 사항을 기록한다.
- [x] 확정 수정 항목이 있으면 코드 수정 전 해당 Phase에 회귀 Task를 추가한다. - [x] 확정 수정 항목이 있으면 코드 수정 전 해당 Phase에 회귀 Task를 추가한다.
- [x] PRD 상태·요구사항 추적표와 이 문서의 체크박스·Progress를 실제 결과로 갱신한다. - [x] PRD 상태·요구사항 추적표와 이 문서의 체크박스·Progress를 실제 결과로 갱신한다.
@@ -711,12 +789,14 @@ Expected: exit code 0, `** BUILD SUCCEEDED **`.
| 6 | `P2-T2` | `P2-T1` | 아니요 | mutation 성공 callback 호출처 재확인 | | 6 | `P2-T2` | `P2-T1` | 아니요 | mutation 성공 callback 호출처 재확인 |
| 7 | `P2-R1` | 사용자 회귀 제보·PRD 반영 | 아니요 | reaction bar 삽입 조건 재확인 | | 7 | `P2-R1` | 사용자 회귀 제보·PRD 반영 | 아니요 | reaction bar 삽입 조건 재확인 |
| 8 | `P2-R2` | 사용자 후속 요청·PRD 반영 | 아니요 | 공지 상세 callback 경계 재확인 | | 8 | `P2-R2` | 사용자 후속 요청·PRD 반영 | 아니요 | 공지 상세 callback 경계 재확인 |
| 9 | `P2-GATE` | Phase 2 Task 전체 | 아니요 | 실패 소유 Task 수정 | | 9 | `P2-R3` | 사용자 후속 요청·인터뷰·PRD 반영 | 아니요 | 상세 댓글 표시·mutation 진입점 재확인 |
| 10 | `P3-T1` | `P2-GATE` | 아니요 | 리뷰 수정 Task 추가 | | 10 | `P2-R4` | 사용자 상태별 수동 검증·PRD 반영 | 아니요 | 공용 리스트 아이템 더보기 조건 재확인 |
| 11 | `P3-GATE` | `P3-T1`·`EXT-001` | 아니요 | 백엔드 반영 후 통합 검증 재개 | | 11 | `P2-GATE` | Phase 2 Task 전체 | 아니요 | 실패 소유 Task 수정 |
| 12 | `P3-T1` | `P2-GATE` | 아니요 | 리뷰 수정 Task 추가 |
| 13 | `P3-GATE` | `P3-T1`·`EXT-001` | 아니요 | 백엔드 반영 후 통합 검증 재개 |
```text ```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 변환하지 않는다. - 홈 API의 `dateUtc`를 임의로 rename·parse·timezone 변환하지 않는다.
- 백엔드 미반영 필드에 optional·fallback 값을 만들어 통합 실패를 숨기지 않는다. - 백엔드 미반영 필드에 optional·fallback 값을 만들어 통합 실패를 숨기지 않는다.
- 신규 protocol·factory·display model·dependency·test target을 추가하지 않는다. - 신규 protocol·factory·display model·dependency·test target을 추가하지 않는다.
- 상세 API의 댓글 payload 계약과 `CreatorChannelReplyDetail/**`를 변경하지 않는다.
- 요청 범위 밖의 기존 dead code·format·naming을 정리하지 않는다. - 요청 범위 밖의 기존 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 기반 통합 검증. - 남은 항목: 기존 `EXT-001`·부팅된 Simulator 기반 통합 검증.
- 다음 행동: 외부 조건 충족 후 `P3-GATE`를 재개한다. - 다음 행동: 외부 조건 충족 후 `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 ## Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | | 날짜 | 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-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-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-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-002` | High | 환경 대기 | 사용 가능한 Simulator가 모두 `Shutdown`이라 실제 터치·메뉴·구매 검증을 실행하지 못했다. | `P3-T1`, `P3-GATE` | Simulator 부팅 후 수동 검증을 재개한다. |
| `ISSUE-003` | Medium | 수정 완료 | 유료 잠금 상태에서 하트·댓글은 숨겨지지만 빈 reaction bar의 고정 24pt 높이가 홈·목록·상세에 남는다. | `P2-R1`, `P3-GATE` | 조건부 삽입 contract check·Debug build 통과. | | `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-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 ```markdown
구현 결과: 홈·커뮤니티 아이템 통일과 상세 구매 일원화 구현 결과: 홈·커뮤니티 아이템 통일과 상세 구매·댓글 잠금 일원화
- 변경: 홈 아이템 재사용, 목록 구매 제거, 더보기·상세·잠금 색상 통일 - 변경: 홈 아이템 재사용, 목록 구매 제거, 더보기·상세·잠금 색상 통일, 상세 유료 미구매 댓글 차단
- 결정: `DEC-001~012`, `PLAN-DEC-001~005` - 결정: `DEC-001~015`, `PLAN-DEC-001~008`
- 검증: - 검증:
- 정적 contract check·`git diff --check` — 실제 결과 - 정적 contract check·`git diff --check` — 실제 결과
- `SodaLive-dev` Debug build — 실제 결과 - `SodaLive-dev` Debug build — 실제 결과

View File

@@ -4,7 +4,7 @@
| 항목 | 내용 | | 항목 | 내용 |
|---|---| |---|---|
| 문서 상태 | 공지 상세 구매 일원화·Debug build 완료·`EXT-001`/Simulator 통합 검증 대기 | | 문서 상태 | `P2-R3` 상태별 수동 검증 완료·`P2-R4` 코드/자동 검증 완료·더보기 상태별 수동 확인 대기 |
| 작성일 | 2026-08-10 | | 작성일 | 2026-08-10 |
| 최종 수정일 | 2026-08-10 | | 최종 수정일 | 2026-08-10 |
| 대상 제품 | SodaLive iOS 크리에이터 채널 홈·커뮤니티 | | 대상 제품 | SodaLive iOS 크리에이터 채널 홈·커뮤니티 |
@@ -15,7 +15,7 @@
## 1. Overview ## 1. Overview
크리에이터 채널 홈 탭의 커뮤니티 요약 아이템을 커뮤니티 탭 리스트와 동일한 `CreatorChannelCommunityListItem`으로 표시한다. 홈의 커뮤니티·공지와 커뮤니티 탭의 아이템 터치는 게시글 상세로 이동하고, 유료 게시글 구매는 상세 화면에서만 시작한다. 유료 미구매 이미지 영역은 상세 화면의 `Color.gray800`과 동일하게 통일하고, 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar의 빈 높이도 제거한다. 크리에이터 채널 홈 탭의 커뮤니티 요약 아이템을 커뮤니티 탭 리스트와 동일한 `CreatorChannelCommunityListItem`으로 표시한다. 홈의 커뮤니티·공지와 커뮤니티 탭의 아이템 터치는 게시글 상세로 이동하고, 유료 게시글 구매는 상세 화면에서만 시작한다. 유료 미구매 이미지 영역은 상세 화면의 `Color.gray800`과 동일하게 통일하고, 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar의 빈 높이도 제거한다. 커뮤니티 상세의 유료 미구매 방문자에게는 댓글 영역을 표시하지 않고 댓글 작성·수정·삭제 동작을 차단한다.
## 2. Problem Statement ## 2. Problem Statement
@@ -25,6 +25,7 @@
- 현재 `CreatorChannelCommunityListItem`의 유료 미구매 영역은 목록에서 구매 다이얼로그를 여는 별도 `Button`이므로, 전체 아이템을 상세 진입으로 통일하려는 요구와 충돌한다. - 현재 `CreatorChannelCommunityListItem`의 유료 미구매 영역은 목록에서 구매 다이얼로그를 여는 별도 `Button`이므로, 전체 아이템을 상세 진입으로 통일하려는 요구와 충돌한다.
- 홈·커뮤니티 리스트와 상세는 하트·댓글을 모두 숨기는 유료 잠금 상태에서도 높이 24pt의 빈 reaction bar를 삽입해 불필요한 세로 공간이 남는다. - 홈·커뮤니티 리스트와 상세는 하트·댓글을 모두 숨기는 유료 잠금 상태에서도 높이 24pt의 빈 reaction bar를 삽입해 불필요한 세로 공간이 남는다.
- 홈 공지 카드는 상세·댓글·구매 callback이 기본 no-op이고, 유료 잠금 영역에 목록 구매용 `Button`이 남아 있어 상세 전용 구매 원칙에서 제외되어 있다. - 홈 공지 카드는 상세·댓글·구매 callback이 기본 no-op이고, 유료 잠금 영역에 목록 구매용 `Button`이 남아 있어 상세 전용 구매 원칙에서 제외되어 있다.
- 커뮤니티 상세는 `isCommentAvailable`만으로 댓글 목록과 입력창을 표시해, 유료 미구매 잠금 상태에서도 댓글을 조회하고 작성·수정·삭제할 수 있다.
## 3. Goals ## 3. Goals
@@ -36,6 +37,7 @@
- 홈 공지 게시글도 본체·댓글·유료 잠금 영역을 상세 진입으로 통일한다. - 홈 공지 게시글도 본체·댓글·유료 잠금 영역을 상세 진입으로 통일한다.
- 유료 미구매 이미지 영역을 `Color.gray800`으로 통일한다. - 유료 미구매 이미지 영역을 `Color.gray800`으로 통일한다.
- 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar가 레이아웃 공간을 차지하지 않게 한다. - 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar가 레이아웃 공간을 차지하지 않게 한다.
- 유료 미구매 방문자는 커뮤니티 상세에서 댓글 영역을 보거나 댓글 작성·수정·삭제 동작을 실행할 수 없게 한다.
### 3.2 UX 목표 ### 3.2 UX 목표
@@ -43,6 +45,7 @@
- 아이템 본문·이미지·유료 잠금 영역을 터치하면 예상 가능하게 상세 화면으로 이동해야 한다. - 아이템 본문·이미지·유료 잠금 영역을 터치하면 예상 가능하게 상세 화면으로 이동해야 한다.
- 더보기·좋아요 같은 독립 제어의 터치는 아이템 상세 터치와 중복 실행되지 않아야 한다. - 더보기·좋아요 같은 독립 제어의 터치는 아이템 상세 터치와 중복 실행되지 않아야 한다.
- 표시할 reaction이 없으면 콘텐츠 아래에 빈 여백이 남지 않아야 한다. - 표시할 reaction이 없으면 콘텐츠 아래에 빈 여백이 남지 않아야 한다.
- 유료 게시글 구매 전에는 상세의 댓글 구분선·목록·입력창이 보이지 않고, 구매 후에는 `isCommentAvailable` 정책에 따라 다시 표시되어야 한다.
## 4. Non-Goals ## 4. Non-Goals
@@ -50,19 +53,23 @@
- 썸네일형 커뮤니티 아이템의 레이아웃은 변경하지 않는다. - 썸네일형 커뮤니티 아이템의 레이아웃은 변경하지 않는다.
- `CommunityPostCard`의 다른 사용처인 메인 홈 팔로잉·추천 섹션은 변경하지 않는다. - `CommunityPostCard`의 다른 사용처인 메인 홈 팔로잉·추천 섹션은 변경하지 않는다.
- 신규 endpoint, 앱 dependency, 범위 밖 커뮤니티 리팩터링은 추가하지 않는다. - 신규 endpoint, 앱 dependency, 범위 밖 커뮤니티 리팩터링은 추가하지 않는다.
- 상세 API 응답의 댓글 payload 계약과 별도 답글 상세 화면은 변경하지 않는다.
## 5. Target Users and Permissions ## 5. Target Users and Permissions
- 타인 채널 방문자: 더보기에서 신고 동작을 사용할 수 있다. - 타인 채널 방문자: 더보기에서 신고 동작을 사용할 수 있다.
- 본인 채널의 크리에이터: 기존 커뮤니티 게시글 관리 권한 범위 안에서 더보기 동작을 사용한다. - 본인 채널의 크리에이터: 기존 커뮤니티 게시글 관리 권한 범위 안에서 더보기 동작을 사용한다.
- 유료 미구매 방문자: 목록에서 구매하지 않고 상세 화면에서 구매를 시작한다. - 유료 미구매 방문자: 목록에서 구매하지 않고 상세 화면에서 구매를 시작한다.
- 유료 미구매 방문자: 구매 전에는 상세의 reaction·댓글 영역을 볼 수 없고 댓글 mutation을 실행할 수 없다.
## 6. 핵심 사용자 흐름 ## 6. 핵심 사용자 흐름
1. 사용자가 크리에이터 채널 홈의 커뮤니티 요약 또는 공지 아이템을 확인한다. 1. 사용자가 크리에이터 채널 홈의 커뮤니티 요약 또는 공지 아이템을 확인한다.
2. 아이템 본체를 터치하면 해당 `postId`의 커뮤니티 게시글 상세로 이동한다. 2. 아이템 본체를 터치하면 해당 `postId`의 커뮤니티 게시글 상세로 이동한다.
3. 유료 미구매 게시글이면 상세 화면에서 기존 구매 다이얼로그와 API를 사용한다. 3. 유료 미구매 게시글이면 상세 화면에서 기존 구매 다이얼로그와 API를 사용한다.
4. 사용자가 우측 상단 더보기를 터치하면 확정된 권한 규칙에 맞는 메뉴가 표시된다. 4. 구매 전에는 상세의 reaction·댓글 영역을 표시하지 않고 댓글 작성·수정·삭제 진입을 차단한다.
5. 구매 성공 후 상세를 다시 조회하고 `isCommentAvailable`에 따라 댓글 영역을 표시한다.
6. 사용자가 우측 상단 더보기를 터치하면 확정된 권한 규칙에 맞는 메뉴가 표시된다.
## 7. 기능 요구사항 ## 7. 기능 요구사항
@@ -78,6 +85,8 @@
| `CCCI-008` | 외부 의존 | 백엔드 홈 API의 `communities`·`notices` 아이템에 `isCommentAvailable: Bool`·`isPinned: Bool`을 커뮤니티 탭과 동일하게 제공한다. | iOS가 두 필드를 임의 기본값 없이 디코딩해 고정·댓글 상태를 표시한다. | `P1-T1`, `EXT-001` | | `CCCI-008` | 외부 의존 | 백엔드 홈 API의 `communities`·`notices` 아이템에 `isCommentAvailable: Bool`·`isPinned: Bool`을 커뮤니티 탭과 동일하게 제공한다. | iOS가 두 필드를 임의 기본값 없이 디코딩해 고정·댓글 상태를 표시한다. | `P1-T1`, `EXT-001` |
| `CCCI-009` | 확정 | 홈·커뮤니티 리스트와 커뮤니티 상세에서 하트·댓글을 모두 표시하지 않을 때 reaction bar를 삽입하지 않는다. | 유료 미구매 잠금 상태에서 하트·댓글과 고정 24pt 높이가 모두 사라지고, 하나라도 표시되면 기존 reaction bar가 유지된다. | `P2-R1` | | `CCCI-009` | 확정 | 홈·커뮤니티 리스트와 커뮤니티 상세에서 하트·댓글을 모두 표시하지 않을 때 reaction bar를 삽입하지 않는다. | 유료 미구매 잠금 상태에서 하트·댓글과 고정 24pt 높이가 모두 사라지고, 하나라도 표시되면 기존 reaction bar가 유지된다. | `P2-R1` |
| `CCCI-010` | 확정 | 홈 공지 게시글도 목록 구매를 제거하고 본체·댓글·유료 잠금 영역을 상세 이동으로 처리한다. | 공지 카드에서 구매 다이얼로그가 직접 열리지 않고 세 영역 모두 선택한 `postId`의 상세로 이동한다. | `P2-R2` | | `CCCI-010` | 확정 | 홈 공지 게시글도 목록 구매를 제거하고 본체·댓글·유료 잠금 영역을 상세 이동으로 처리한다. | 공지 카드에서 구매 다이얼로그가 직접 열리지 않고 세 영역 모두 선택한 `postId`의 상세로 이동한다. | `P2-R2` |
| `CCCI-011` | 확정 | 커뮤니티 상세의 유료 미구매 잠금 상태에서는 댓글 영역을 표시하지 않고 댓글 작성·수정·삭제 동작을 차단한다. | 댓글 구분선·제목·목록·하단 입력창이 View 계층에 없고 ViewModel의 댓글 mutation 진입점이 API 요청을 실행하지 않는다. 구매 후에는 `isCommentAvailable`에 따라 기존 댓글 UI와 동작이 복원된다. | `P2-R3` |
| `CCCI-012` | 확정 | 홈·커뮤니티 탭 리스트 아이템의 더보기는 유료 미구매 타인 게시글에서 숨기고, 무료·구매 완료 게시글과 작성자 본인 게시글에서 표시한다. | `CreatorChannelCommunityListItem`의 더보기가 유료 미구매 타인에게 없고 나머지 상태에는 표시된다. | `P2-R4` |
## 8. UI/UX Expectations ## 8. UI/UX Expectations
@@ -87,6 +96,8 @@
- 리스트형 유료 미구매 영역은 236pt 높이를, 썸네일형은 기존 정사각형 비율을 유지한다. 두 형태 모두 잠금 icon·가격 capsule을 유지하고 배경만 `Color.gray800`으로 통일한다. - 리스트형 유료 미구매 영역은 236pt 높이를, 썸네일형은 기존 정사각형 비율을 유지한다. 두 형태 모두 잠금 icon·가격 capsule을 유지하고 배경만 `Color.gray800`으로 통일한다.
- 더보기와 좋아요 제어는 자신의 action만 실행하고 상세 이동을 중복 실행하지 않는다. - 더보기와 좋아요 제어는 자신의 action만 실행하고 상세 이동을 중복 실행하지 않는다.
- 유료 미구매 잠금 상태처럼 하트·댓글을 모두 숨기는 경우 reaction bar 전체를 조건부로 제외해 고정 높이와 `VStack` 간격을 남기지 않는다. - 유료 미구매 잠금 상태처럼 하트·댓글을 모두 숨기는 경우 reaction bar 전체를 조건부로 제외해 고정 높이와 `VStack` 간격을 남기지 않는다.
- 커뮤니티 상세의 유료 미구매 잠금 상태에서는 본문 아래 댓글 구분선·댓글 제목과 목록·하단 입력창을 모두 삽입하지 않는다.
- 홈·커뮤니티 탭 리스트 아이템의 우측 상단 더보기는 유료 미구매 타인 게시글에서 레이아웃과 hit-testing 모두 제거하고, 작성자에게는 구매 여부와 무관하게 항상 표시한다.
## 9. 기술·운영 제약 ## 9. 기술·운영 제약
@@ -96,6 +107,8 @@
- 백엔드는 홈 API 응답에 커뮤니티 탭과 동일한 `isCommentAvailable: Bool`·`isPinned: Bool`을 제공한다. iOS는 두 필드가 없는 응답을 추정 기본값으로 보정하지 않는다. - 백엔드는 홈 API 응답에 커뮤니티 탭과 동일한 `isCommentAvailable: Bool`·`isPinned: Bool`을 제공한다. iOS는 두 필드가 없는 응답을 추정 기본값으로 보정하지 않는다.
- 홈 응답 모델과 커뮤니티 탭 응답 모델의 차이를 제거하기 위한 투기적 protocol·factory·신규 dependency를 추가하지 않는다. - 홈 응답 모델과 커뮤니티 탭 응답 모델의 차이를 제거하기 위한 투기적 protocol·factory·신규 dependency를 추가하지 않는다.
- 본인 게시글 판정은 기존처럼 `creatorId == UserDefaults.int(forKey: .userId)`를 사용한다. - 본인 게시글 판정은 기존처럼 `creatorId == UserDefaults.int(forKey: .userId)`를 사용한다.
- 유료 미구매 잠금 판정은 기존처럼 `price > 0 && existOrdered == false && isOwnPost == false`를 사용하며, 본인 게시글에는 댓글 차단을 적용하지 않는다.
- 더보기 노출도 같은 유료 미구매 잠금 판정을 재사용하고 별도 권한 상태를 추가하지 않는다.
## 10. 성공 기준 ## 10. 성공 기준
@@ -108,6 +121,9 @@
- [ ] 더보기·좋아요 터치가 상세 이동을 중복 발생시키지 않는다. - [ ] 더보기·좋아요 터치가 상세 이동을 중복 발생시키지 않는다.
- [x] 홈·커뮤니티 리스트와 상세의 유료 미구매 상태에서 하트·댓글용 빈 공간이 남지 않는다. - [x] 홈·커뮤니티 리스트와 상세의 유료 미구매 상태에서 하트·댓글용 빈 공간이 남지 않는다.
- [ ] 홈 공지의 본체·댓글·유료 잠금 영역이 상세로 이동하고 공지 카드에서 구매 다이얼로그가 직접 열리지 않는다. - [ ] 홈 공지의 본체·댓글·유료 잠금 영역이 상세로 이동하고 공지 카드에서 구매 다이얼로그가 직접 열리지 않는다.
- [x] 커뮤니티 상세의 유료 미구매 상태에서 댓글 구분선·목록·입력창이 보이지 않고 작성·수정·삭제 API 요청이 실행되지 않는다.
- [x] 무료·구매 완료·본인 게시글은 기존 `isCommentAvailable` 값에 따라 댓글 영역과 동작을 유지한다.
- [ ] 홈·커뮤니티 탭 리스트 아이템의 더보기는 유료 미구매 타인 게시글에서 숨고, 무료·구매 완료·작성자 게시글에서 표시된다.
- [x] `SodaLive-dev` 스킴 Debug 빌드가 성공한다. - [x] `SodaLive-dev` 스킴 Debug 빌드가 성공한다.
- [ ] 시뮬레이터에서 홈·커뮤니티 탭·상세의 아이템 터치, 더보기, 유료 잠금 색상을 확인한다. - [ ] 시뮬레이터에서 홈·커뮤니티 탭·상세의 아이템 터치, 더보기, 유료 잠금 색상을 확인한다.
@@ -119,6 +135,7 @@
| `OQ-002` | 확정 | 목록 구매 제거를 홈과 커뮤니티 탭 모두에 적용한다. | A안 확정 | 사용자 | 2026-08-10 | `CCCI-004`, `CCCI-007` | | `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-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-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. 외부 제공 대기 계약 ## 12. 외부 제공 대기 계약
@@ -135,6 +152,7 @@
| `CCCI-008` | 홈 API 필드 확장 | `P1-T1`, `P3-GATE`, `EXT-001` | decoding contract·Debug build | 백엔드 반영 후 홈 응답 확인 | | `CCCI-008` | 홈 API 필드 확장 | `P1-T1`, `P3-GATE`, `EXT-001` | decoding contract·Debug build | 백엔드 반영 후 홈 응답 확인 |
| `CCCI-009` | 기존 목록·상세 모델 재사용 | `P2-R1` | reaction bar 조건부 삽입 contract check·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-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 ## 14. Decision Log
@@ -154,6 +172,8 @@
| 2026-08-10 | `DEC-012` | 확정 | 목록에 있던 기존 구매 다이얼로그·API 호출 소유권을 상세 화면으로 옮긴다. | 구현 전 기준선에서 상세 잠금 버튼이 구매가 아닌 `showPurchaseError()`만 호출함을 확인·사용자 직접 요구 충족 | `CCCI-004`, `CCCI-007` | | 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-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-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. 구현 설계 ## 15. 구현 설계
@@ -173,7 +193,10 @@
- 상세 진입 helper는 표시 모델 전체 대신 `postId` 만 받아 홈·리스트형·썸네일형이 같은 route를 사용한다. - 상세 진입 helper는 표시 모델 전체 대신 `postId` 만 받아 홈·리스트형·썸네일형이 같은 route를 사용한다.
- 더보기·좋아요 `Button`은 각자의 action만 실행하고 부모 아이템의 상세 진입을 중복 실행하지 않아야 한다. - 더보기·좋아요 `Button`은 각자의 action만 실행하고 부모 아이템의 상세 진입을 중복 실행하지 않아야 한다.
- `CreatorChannelCommunityListItem``CreatorChannelCommunityPostDetailContentView``isPaidLocked``true`이면 `reactionBar` 자체를 View 계층에 삽입하지 않는다. 댓글만 비활성이고 좋아요가 표시되는 상태는 기존 reaction bar를 유지한다. - `CreatorChannelCommunityListItem``CreatorChannelCommunityPostDetailContentView``isPaidLocked``true`이면 `reactionBar` 자체를 View 계층에 삽입하지 않는다. 댓글만 비활성이고 좋아요가 표시되는 상태는 기존 reaction bar를 유지한다.
- `CreatorChannelCommunityPostDetailViewModel`은 상세의 `isCommentAvailable`과 기존 유료 잠금 조건을 함께 판정하는 단일 댓글 상호작용 상태를 제공한다. 상세 View는 이 상태가 `true`일 때만 댓글 구분선·목록·하단 입력창을 삽입한다.
- 같은 상태를 댓글 전송 가능 여부와 작성·수정·삭제 mutation 진입 guard에 재사용한다. 잠금 상태에서는 댓글 API 요청을 시작하지 않는다.
- `CreatorChannelNoticeSection`은 본체·댓글·유료 잠금 영역에 하나의 상세 callback을 사용하고, `CreatorChannelHomeView`의 기존 `onTapCommunityDetail`을 전달한다. 공지 카드의 외형은 변경하지 않는다. - `CreatorChannelNoticeSection`은 본체·댓글·유료 잠금 영역에 하나의 상세 callback을 사용하고, `CreatorChannelHomeView`의 기존 `onTapCommunityDetail`을 전달한다. 공지 카드의 외형은 변경하지 않는다.
- `CreatorChannelCommunityListItem.header`의 방문자 더보기 분기를 `!isPaidLocked`로 제한한다. 기존 작성자 제어 분기·가격 표시·callback은 유지한다.
### 15.3 더보기와 변경 후 갱신 ### 15.3 더보기와 변경 후 갱신
@@ -197,6 +220,8 @@
- 정적 검증은 `rg`로 목록 `onTapPurchase`·목록 구매 dialog 호출처 제거, 상세 구매 dialog·API 연결, `Color.gray800`, 홈·목록·썸네일 상세 route 연결을 확인하고 `git diff --check`를 실행한다. - 정적 검증은 `rg`로 목록 `onTapPurchase`·목록 구매 dialog 호출처 제거, 상세 구매 dialog·API 연결, `Color.gray800`, 홈·목록·썸네일 상세 route 연결을 확인하고 `git diff --check`를 실행한다.
- reaction bar 회귀 검증은 두 대상 View의 body에 무조건 삽입된 `reactionBar`가 없고 `if !isPaidLocked` 내부에만 삽입되는지 focused `rg`로 확인한다. - reaction bar 회귀 검증은 두 대상 View의 body에 무조건 삽입된 `reactionBar`가 없고 `if !isPaidLocked` 내부에만 삽입되는지 focused `rg`로 확인한다.
- 공지 회귀 검증은 `CreatorChannelNoticeSection``onTapPurchase`·`onTapNotice`·`onTapComment`가 제거되고 단일 상세 callback이 `CreatorChannelHomeView.onTapCommunityDetail`에 연결되는지 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`로 컴파일·링크 성공을 확인한다. - 자동 검증은 `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build`로 컴파일·링크 성공을 확인한다.
- `EXT-001` 반영 후 시뮬레이터에서 홈·리스트형·썸네일형의 상세 진입, 더보기 권한 메뉴, 목록 구매 dialog 미노출, 상세 구매, `Color.gray800`을 수동 확인한다. - `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: 두 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: `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: 공지 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을 확인했다.

View File

@@ -4,18 +4,18 @@
| 항목 | 내용 | | 항목 | 내용 |
|---|---| |---|---|
| 리뷰 대상 | Phase 1~2 / `P1-T1~P2-R2` | | 리뷰 대상 | Phase 1~2 / `P1-T1~P2-R4` |
| 기준 commit 또는 working tree | `89c070875cbeea8395c1e90cb9f47ff80df5d90f` 이후 미커밋 변경 | | 기준 commit 또는 working tree | `89c070875cbeea8395c1e90cb9f47ff80df5d90f` 이후 미커밋 변경 |
| 리뷰 일자 | 2026-08-10 | | 리뷰 일자 | 2026-08-10 |
| 리뷰어 | Codex 자가 리뷰—프로젝트 보수적 실행 정책에 따라 subagent 미사용 | | 리뷰어 | Sisyphus 구현 검토·Oracle 독립 검토 |
| 기준 문서 | `docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md`, `plan-task.md` | | 기준 문서 | `docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md`, `plan-task.md` |
| 리뷰 상태 | `REV-P3-004`정 완료·외부 통합 검증 대기 | | 리뷰 상태 | `REV-P3-005`동 검증 완료·`REV-P3-006` 코드/자동 검증 완료·상태별 더보기 확인 대기 |
## 2. 리뷰 목적과 범위 ## 2. 리뷰 목적과 범위
### 목적 ### 목적
- 홈·커뮤니티 탭의 아이템과 공지, 상세 진입, 더보기, 목록 구매 제거, 상세 구매 이동이 `CCCI-001~010` 충족하는지 확인한다. - 홈·커뮤니티 탭의 아이템과 공지, 상세 진입, 더보기 노출, 목록 구매 제거, 상세 구매 이동·댓글 잠금`CCCI-001~012` 충족하는지 확인한다.
- 코드·문서·검증 기록이 실제 working tree와 일치하는지 확인한다. - 코드·문서·검증 기록이 실제 working tree와 일치하는지 확인한다.
### 포함 범위 ### 포함 범위
@@ -50,8 +50,8 @@
### 문서와 코드 ### 문서와 코드
- 요구사항: `CCCI-001~010`, `DEC-001~014`, `EXT-001` - 요구사항: `CCCI-001~012`, `DEC-001~016`, `EXT-001`
- 계획: `P1-T1~P2-T2`, `P2-R1`, `P2-R2`, `P1-GATE`, `P2-GATE` - 계획: `P1-T1~P2-T2`, `P2-R1`, `P2-R2`, `P2-R3`, `P2-R4`, `P1-GATE`, `P2-GATE`
- 홈 변환·아이템: `CreatorChannelCommunitySection.swift:3` - 홈 변환·아이템: `CreatorChannelCommunitySection.swift:3`
- 목록 잠금·상세 gesture: `CreatorChannelCommunityListItem.swift`, `CreatorChannelCommunityGridItem.swift` - 목록 잠금·상세 gesture: `CreatorChannelCommunityListItem.swift`, `CreatorChannelCommunityGridItem.swift`
- 상세 구매: `CreatorChannelCommunityPostDetailView.swift:19`, `CreatorChannelCommunityPostDetailViewModel.swift:186` - 상세 구매: `CreatorChannelCommunityPostDetailView.swift:19`, `CreatorChannelCommunityPostDetailViewModel.swift:186`
@@ -76,9 +76,13 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함
| 상세 구매 `rg` contract check | 성공 | `showPurchaseError` 0건, 상세 dialog·repository 호출 match | | 상세 구매 `rg` contract check | 성공 | `showPurchaseError` 0건, 상세 dialog·repository 호출 match |
| reaction bar 조건부 삽입 `rg` contract check | 성공 | 무조건 삽입 0건, `if !isPaidLocked` 조건부 삽입 2건 | | 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 직접 구매 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건 | | `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 | | `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. 발견 사항 요약 ## 5. 발견 사항 요약
@@ -90,6 +94,8 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함
| `REV-P3-002` | High | 보류 | 홈 API 신규 필드와 런타임 동작의 통합 검증 필요 | `P3-GATE`, `EXT-001` | 외부 반영 후 재개 | | `REV-P3-002` | High | 보류 | 홈 API 신규 필드와 런타임 동작의 통합 검증 필요 | `P3-GATE`, `EXT-001` | 외부 반영 후 재개 |
| `REV-P3-003` | Medium | 수정 완료 | 하트·댓글이 숨겨져도 빈 reaction bar가 24pt 공간을 차지함 | `P2-R1` | 완료 | | `REV-P3-003` | Medium | 수정 완료 | 하트·댓글이 숨겨져도 빈 reaction bar가 24pt 공간을 차지함 | `P2-R1` | 완료 |
| `REV-P3-004` | High | 수정 완료 | 홈 공지에 목록 구매 callback이 남고 상세 callback이 연결되지 않음 | `P2-R2` | 완료 | | `REV-P3-004` | High | 수정 완료 | 홈 공지에 목록 구매 callback이 남고 상세 callback이 연결되지 않음 | `P2-R2` | 완료 |
| `REV-P3-005` | High | 수정·수동 검증 완료 | 유료 미구매 상세가 댓글 UI와 mutation을 허용함 | `P2-R3` | 완료 |
| `REV-P3-006` | Medium | 코드·자동 검증 완료·수동 대기 | 유료 미구매 타인 게시글에도 더보기가 표시됨 | `P2-R4` | 홈·커뮤니티 상태별 확인 |
## 6. 발견 사항 상세 ## 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가 통과했다. `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 전환 ## 7. 확정 항목의 plan·goal 전환
- `REV-P3-001`: 코드 수정 전에 `P1-T3`로 전환했고 수정·빌드를 완료했다. - `REV-P3-001`: 코드 수정 전에 `P1-T3`로 전환했고 수정·빌드를 완료했다.
- `REV-P3-003`: 코드 수정 전에 `P2-R1`로 전환했고 수정·검증을 완료했다. - `REV-P3-003`: 코드 수정 전에 `P2-R1`로 전환했고 수정·검증을 완료했다.
- `REV-P3-004`: 코드 수정 전에 `P2-R2`로 전환했고 수정·검증을 완료했다. - `REV-P3-004`: 코드 수정 전에 `P2-R2`로 전환했고 수정·검증을 완료했다.
- `REV-P3-005`: 코드 수정 전에 `P2-R3`로 전환했고 코드·자동·상태별 수동 검증을 완료했다.
- `REV-P3-006`: 코드 수정 전에 `P2-R4`로 전환했고 코드·자동 검증을 완료했다. 상태별 더보기 수동 확인은 대기한다.
- `REV-P3-002`: 외부 조건이므로 `P3-GATE` 완료를 보류한다. - `REV-P3-002`: 외부 조건이므로 `P3-GATE` 완료를 보류한다.
## 8. 리뷰 종료 판정 ## 8. 리뷰 종료 판정
@@ -146,9 +172,9 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함
| 보류 항목의 담당·재개 조건 기록 | 충족 | `EXT-001`·부팅된 Simulator | | 보류 항목의 담당·재개 조건 기록 | 충족 | `EXT-001`·부팅된 Simulator |
| 검증 명령과 결과 기록 | 충족 | 정적 계약·Debug build exit 0 | | 검증 명령과 결과 기록 | 충족 | 정적 계약·Debug build exit 0 |
**최종 결론:** 홈 공지 상세 구매 일원화 수정·빌드 완료, 외부 통합 검증 대기 **최종 결론:** 상세 댓글 차단 상태별 검증 완료, 더보기 노출 조건 코드·자동 검증 완료·상태별 수동 확인 대기
**남은 항목:** 백엔드 `EXT-001` 반영 후 부팅된 Simulator에서 본체 상세 이동, 권한별 더보기, 목록 dialog 미노출, 상세 구매, `Color.gray800`을 확인한다. **남은 항목:** 홈·커뮤니티 리스트에서 더보기 네 상태를 수동 확인하고, 백엔드 `EXT-001` 반영 후 나머지 통합 흐름을 확인한다.
## 9. 수정 후 검증 기록 ## 9. 수정 후 검증 기록
@@ -183,3 +209,28 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함
- `git diff --check` — 성공 - `git diff --check` — 성공
- `SodaLive-dev` Debug build — exit code 0 - `SodaLive-dev` Debug build — exit code 0
- 남은 항목: `REV-P3-002`와 Simulator 통합 검증 - 남은 항목: `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건.