fix(creator): 공지 상세 이동을 연결한다

This commit is contained in:
Yu Sung
2026-08-10 15:42:46 +09:00
parent c372d4a522
commit 00c40161c9
5 changed files with 145 additions and 61 deletions

View File

@@ -2,20 +2,14 @@ import SwiftUI
struct CreatorChannelNoticeSection: View { struct CreatorChannelNoticeSection: View {
let notices: [CreatorChannelCommunityPostResponse] let notices: [CreatorChannelCommunityPostResponse]
let onTapNotice: (Int) -> Void let onTapDetail: (Int) -> Void
let onTapComment: (Int) -> Void
let onTapPurchase: (CreatorChannelCommunityPostResponse) -> Void
init( init(
notices: [CreatorChannelCommunityPostResponse], notices: [CreatorChannelCommunityPostResponse],
onTapNotice: @escaping (Int) -> Void = { _ in }, onTapDetail: @escaping (Int) -> Void = { _ in }
onTapComment: @escaping (Int) -> Void = { _ in },
onTapPurchase: @escaping (CreatorChannelCommunityPostResponse) -> Void = { _ in }
) { ) {
self.notices = notices self.notices = notices
self.onTapNotice = onTapNotice self.onTapDetail = onTapDetail
self.onTapComment = onTapComment
self.onTapPurchase = onTapPurchase
} }
var body: some View { var body: some View {
@@ -28,14 +22,8 @@ struct CreatorChannelNoticeSection: View {
ForEach(notices) { notice in ForEach(notices) { notice in
CreatorChannelNoticeCard( CreatorChannelNoticeCard(
notice: notice, notice: notice,
onTapNotice: { onTapDetail: {
onTapNotice(notice.postId) onTapDetail(notice.postId)
},
onTapComment: {
onTapComment(notice.postId)
},
onTapPurchase: {
onTapPurchase(notice)
} }
) )
.frame(width: 374) .frame(width: 374)
@@ -51,9 +39,7 @@ struct CreatorChannelNoticeSection: View {
private struct CreatorChannelNoticeCard: View { private struct CreatorChannelNoticeCard: View {
let notice: CreatorChannelCommunityPostResponse let notice: CreatorChannelCommunityPostResponse
let onTapNotice: () -> Void let onTapDetail: () -> Void
let onTapComment: () -> Void
let onTapPurchase: () -> Void
@StateObject private var playManager = CreatorCommunityMediaPlayerManager.shared @StateObject private var playManager = CreatorCommunityMediaPlayerManager.shared
@StateObject private var contentPlayManager = ContentPlayManager.shared @StateObject private var contentPlayManager = ContentPlayManager.shared
@@ -69,7 +55,7 @@ private struct CreatorChannelNoticeCard: View {
.background(Color.gray900) .background(Color.gray900)
.clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous))
.contentShape(Rectangle()) .contentShape(Rectangle())
.onTapGesture(perform: onTapNotice) .onTapGesture(perform: onTapDetail)
} }
private var noticeLabel: some View { private var noticeLabel: some View {
@@ -164,40 +150,37 @@ private struct CreatorChannelNoticeCard: View {
} }
private var paidLockOverlay: some View { private var paidLockOverlay: some View {
Button(action: onTapPurchase) { ZStack {
ZStack { Color.gray700.opacity(0.35)
Color.gray700.opacity(0.35)
VStack(spacing: SodaSpacing.s4) { VStack(spacing: SodaSpacing.s4) {
Image("ic_new_community_lock") Image("ic_new_community_lock")
.resizable()
.renderingMode(.template)
.foregroundColor(.white)
.frame(width: 24, height: 24)
HStack(spacing: SodaSpacing.s6) {
Image("ic_bar_cash")
.resizable() .resizable()
.renderingMode(.template) .frame(width: 20, height: 20)
.foregroundColor(.white)
.frame(width: 24, height: 24)
HStack(spacing: SodaSpacing.s6) { Text("\(notice.price)")
Image("ic_bar_cash") .appFont(.body2)
.resizable() .foregroundColor(.black)
.frame(width: 20, height: 20)
Text("\(notice.price)")
.appFont(.body2)
.foregroundColor(.black)
}
.padding(SodaSpacing.s8)
.background(Color.white)
.clipShape(Capsule())
} }
.padding(SodaSpacing.s8)
.background(Color.white)
.clipShape(Capsule())
} }
} }
.buttonStyle(.plain)
.frame(width: 92, height: 92) .frame(width: 92, height: 92)
.clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous))
} }
private var reactionBar: some View { private var reactionBar: some View {
HStack(spacing: 15) { HStack(spacing: 15) {
Button(action: onTapComment) { Button(action: onTapDetail) {
HStack(spacing: SodaSpacing.s4) { HStack(spacing: SodaSpacing.s4) {
Image("ic_feed_community_reply") Image("ic_feed_community_reply")
.resizable() .resizable()

View File

@@ -57,7 +57,10 @@ struct CreatorChannelHomeView: View {
onTapDonate: onTapDonate onTapDonate: onTapDonate
) )
CreatorChannelNoticeSection(notices: response.notices) CreatorChannelNoticeSection(
notices: response.notices,
onTapDetail: onTapCommunityDetail
)
CreatorChannelScheduleSection( CreatorChannelScheduleSection(
schedules: response.schedules, schedules: response.schedules,

View File

@@ -570,6 +570,50 @@ Expected: 첫 명령 match 0건·exit code 1, 둘째 명령 match 2건, whitespa
- [x] **REFACTOR:** 신규 상태·helper를 추가하지 않고 기존 `isPaidLocked`만 재사용한다. - [x] **REFACTOR:** 신규 상태·helper를 추가하지 않고 기존 `isPaidLocked`만 재사용한다.
### Regression Task 2.4 공지 구매를 상세 흐름으로 일원화
**Goal 실행 `P2-R2`:** 홈 공지의 본체·댓글·유료 잠금 영역이 기존 커뮤니티 상세로 이동하고 구매는 상세에서만 시작한다.
- **리뷰 근거:** `REV-P3-004``reviews/phase3-community-item-unification.md`
- **시작 조건:** 사용자 후속 요청과 코드 근거 확인, PRD `CCCI-010`·`DEC-014` 반영.
- **완료 증거:** 공지 구매 callback 0건, 단일 상세 callback과 홈 `onTapCommunityDetail` 연결, `git diff --check`, Debug build.
- **범위 밖:** 공지 카드 외형·오디오 재생 동작, 구매 endpoint·dialog 내부 UI, 신규 helper·test target 추가.
**Files:**
- Modify: `SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift`
- Modify: `SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift`
- Test: 신규 test 파일 없음—focused contract check·Debug build 사용
**Interfaces:**
- Consumes: `CreatorChannelHomeView.onTapCommunityDetail`, `CreatorChannelView.showCommunityPostDetail(_:)`, 상세 구매·refresh 흐름.
- Produces: 공지 카드의 모든 게시글 진입점이 같은 `postId` 상세 route를 사용하는 동작.
- [x] **RED:** 공지에 별도 `onTapPurchase`·`onTapNotice`·`onTapComment`가 남고 홈 조립부가 상세 callback을 전달하지 않음을 확인한다.
```bash
rg -n "onTapPurchase|onTapNotice|onTapComment|Button\(action: onTapPurchase\)" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift
rg -n "CreatorChannelNoticeSection\(notices: response\.notices\)" SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift
```
Expected: 두 명령 모두 match·exit code 0.
- [x] **RED 확인:** 상세 route와 상세 구매 성공 후 상세·홈·커뮤니티 refresh 흐름은 이미 존재함을 확인한다.
- [x] **GREEN:** 공지의 세 callback을 단일 `onTapDetail`로 교체하고 잠금 구매 `Button` wrapper를 제거한 뒤 홈의 `onTapCommunityDetail`을 전달한다.
- [x] **GREEN 확인:** 기존 공지 callback 0건, 단일 상세 callback·홈 연결 match, `git diff --check`, Debug build가 통과함을 확인한다.
```bash
rg -n "onTapPurchase|onTapNotice|onTapComment|Button\(action: onTapPurchase\)" 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
git diff --check
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build
```
Expected: 첫 명령 match 0건·exit code 1, 둘째 명령 필수 연결 match, whitespace 오류 0건, `** BUILD SUCCEEDED **`.
- [x] **REFACTOR:** 공지 카드 외형·오디오 재생과 기존 상세·구매·refresh 구현은 변경하지 않는다.
### Phase 2 Gate ### Phase 2 Gate
**Goal 실행 `P2-GATE`:** 홈 재사용·상세·더보기·목록 동기화 구조를 판정한다. **Goal 실행 `P2-GATE`:** 홈 재사용·상세·더보기·목록 동기화 구조를 판정한다.
@@ -579,10 +623,12 @@ git diff --check
rg -n "communityPostItem|CreatorChannelCommunityListItem\(|onTapCommunityMore|onTapCommunityDetail|refreshCommunityFeeds" SodaLive/Sources/V2/CreatorChannel/Home SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift rg -n "communityPostItem|CreatorChannelCommunityListItem\(|onTapCommunityMore|onTapCommunityDetail|refreshCommunityFeeds" SodaLive/Sources/V2/CreatorChannel/Home SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
rg -n "CommunityPostCard\(" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift rg -n "CommunityPostCard\(" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.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 -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
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` match 0건·exit code 1, `** BUILD SUCCEEDED **`. Expected: whitespace 오류 0건, 필수 연결 match, 홈 `CommunityPostCard`·공지 기존 callback match 0건·exit code 1, `** BUILD SUCCEEDED **`.
## Phase 3: 리뷰·통합 검증·문서 종결 ## Phase 3: 리뷰·통합 검증·문서 종결
@@ -620,6 +666,8 @@ Expected: whitespace 오류 0건, 필수 연결 match, 홈 `CommunityPostCard` m
rg -n "let (isCommentAvailable|isPinned): Bool" SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift rg -n "let (isCommentAvailable|isPinned): Bool" SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift
rg -n "communityPostItem|CreatorChannelCommunityListItem\(|showCommunityPostDetail\(_ postId: Int\)|refreshCommunityFeeds" SodaLive/Sources/V2/CreatorChannel/Home SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift rg -n "communityPostItem|CreatorChannelCommunityListItem\(|showCommunityPostDetail\(_ postId: Int\)|refreshCommunityFeeds" SodaLive/Sources/V2/CreatorChannel/Home SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
rg -n "onTapPurchase|openPurchaseDialog|isShowPostPurchaseView|selectedPostPrice|purchaseCommunityPost" SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityTabView.swift SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityViewModel.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift rg -n "onTapPurchase|openPurchaseDialog|isShowPostPurchaseView|selectedPostPrice|purchaseCommunityPost" SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityTabView.swift SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityViewModel.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.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 "Color\.gray800" SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift rg -n "Color\.gray800" SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift
git diff --check git diff --check
``` ```
@@ -662,12 +710,13 @@ Expected: exit code 0, `** BUILD SUCCEEDED **`.
| 5 | `P2-T1` | `P1-GATE` | 아니요 | 홈·탭 모델 매핑 재확인 | | 5 | `P2-T1` | `P1-GATE` | 아니요 | 홈·탭 모델 매핑 재확인 |
| 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-GATE` | Phase 2 Task 전체 | 아니요 | 실패 소유 Task 수정 | | 8 | `P2-R2` | 사용자 후속 요청·PRD 반영 | 아니요 | 공지 상세 callback 경계 재확인 |
| 9 | `P3-T1` | `P2-GATE` | 아니요 | 리뷰 수정 Task 추가 | | 9 | `P2-GATE` | Phase 2 Task 전체 | 아니요 | 실패 소유 Task 수정 |
| 10 | `P3-GATE` | `P3-T1`·`EXT-001` | 아니요 | 백엔드 반영 후 통합 검증 재개 | | 10 | `P3-T1` | `P2-GATE` | 아니요 | 리뷰 수정 Task 추가 |
| 11 | `P3-GATE` | `P3-T1`·`EXT-001` | 아니요 | 백엔드 반영 후 통합 검증 재개 |
```text ```text
P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-GATE → P3-T1 → P3-GATE P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-R2 → P2-GATE → P3-T1 → P3-GATE
``` ```
## 변경 금지 항목 ## 변경 금지 항목
@@ -749,6 +798,19 @@ P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-GATE
- 남은 항목: 기존 `EXT-001`·부팅된 Simulator 기반 Preview/통합 검증. - 남은 항목: 기존 `EXT-001`·부팅된 Simulator 기반 Preview/통합 검증.
- 다음 행동: 외부 조건 충족 후 `P3-GATE`를 재개한다. - 다음 행동: 외부 조건 충족 후 `P3-GATE`를 재개한다.
### 공지 상세 구매 일원화 — 2026-08-10
- 상태: `P2-R2` 구현·정적 계약·Debug build 완료
- 무엇을: 홈 공지의 별도 구매 callback과 no-op 상세·댓글 callback을 하나의 상세 이동 흐름으로 통일한다.
- 왜: 공지도 커뮤니티 게시글이므로 목록에서 구매하지 않고 상세에서만 구매해야 한다.
- 어떻게: `REV-P3-004`, `CCCI-010`, `DEC-014`를 연결하고 focused RED/GREEN contract check와 Debug build를 완료 증거로 사용한다.
- 결과:
- RED: 공지 직접 구매 callback 7건과 상세 callback 미전달 생성부 1건을 확인했다.
- GREEN: 기존 공지 callback 0건, 단일 `onTapDetail`과 홈 `onTapCommunityDetail` 연결을 확인했다.
- `git diff --check` 오류 0건, `SodaLive-dev` Debug build exit code 0을 확인했다.
- 남은 항목: 기존 `EXT-001`·부팅된 Simulator 기반 통합 검증.
- 다음 행동: 외부 조건 충족 후 `P3-GATE`를 재개한다.
## Decision Log ## Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | | 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
@@ -759,6 +821,7 @@ P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-GATE
| 2026-08-10 | `PLAN-DEC-004` | 확정 | 신규 test target 대신 contract check·Preview·Debug build·시뮬레이터를 완료 증거로 사용한다. | 현재 프로젝트에 unit test bundle 없음 | 전체 Task·Gate | | 2026-08-10 | `PLAN-DEC-004` | 확정 | 신규 test target 대신 contract check·Preview·Debug build·시뮬레이터를 완료 증거로 사용한다. | 현재 프로젝트에 unit test bundle 없음 | 전체 Task·Gate |
| 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` |
## 발견된 문제 ## 발견된 문제
@@ -767,6 +830,7 @@ P1-T1 → P1-T2 → P1-T3 → P1-GATE → P2-T1 → P2-T2 → P2-R1 → P2-GATE
| `ISSUE-001` | High | 외부 조건 대기 | 백엔드 홈 API 응답에 `isCommentAvailable`·`isPinned`이 없으면 신규 iOS 모델 디코딩에 실패한다. | `P1-T1`, `P3-GATE` | iOS 구현·빌드는 완료했고 `EXT-001` 반영 후 통합 검증한다. | | `ISSUE-001` | High | 외부 조건 대기 | 백엔드 홈 API 응답에 `isCommentAvailable`·`isPinned`이 없으면 신규 iOS 모델 디코딩에 실패한다. | `P1-T1`, `P3-GATE` | iOS 구현·빌드는 완료했고 `EXT-001` 반영 후 통합 검증한다. |
| `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 통과. |
## 최종 보고 형식 ## 최종 보고 형식

View File

@@ -4,7 +4,7 @@
| 항목 | 내용 | | 항목 | 내용 |
|---|---| |---|---|
| 문서 상태 | reaction bar 회귀 수정·Debug build 완료·`EXT-001`/Simulator 통합 검증 대기 | | 문서 상태 | 공지 상세 구매 일원화·Debug build 완료·`EXT-001`/Simulator 통합 검증 대기 |
| 작성일 | 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
@@ -24,6 +24,7 @@
- 커뮤니티 탭의 유료 미구매 영역은 `Color.gray400`, 상세 화면은 `Color.gray800`을 사용해 색상이 다르다. - 커뮤니티 탭의 유료 미구매 영역은 `Color.gray400`, 상세 화면은 `Color.gray800`을 사용해 색상이 다르다.
- 현재 `CreatorChannelCommunityListItem`의 유료 미구매 영역은 목록에서 구매 다이얼로그를 여는 별도 `Button`이므로, 전체 아이템을 상세 진입으로 통일하려는 요구와 충돌한다. - 현재 `CreatorChannelCommunityListItem`의 유료 미구매 영역은 목록에서 구매 다이얼로그를 여는 별도 `Button`이므로, 전체 아이템을 상세 진입으로 통일하려는 요구와 충돌한다.
- 홈·커뮤니티 리스트와 상세는 하트·댓글을 모두 숨기는 유료 잠금 상태에서도 높이 24pt의 빈 reaction bar를 삽입해 불필요한 세로 공간이 남는다. - 홈·커뮤니티 리스트와 상세는 하트·댓글을 모두 숨기는 유료 잠금 상태에서도 높이 24pt의 빈 reaction bar를 삽입해 불필요한 세로 공간이 남는다.
- 홈 공지 카드는 상세·댓글·구매 callback이 기본 no-op이고, 유료 잠금 영역에 목록 구매용 `Button`이 남아 있어 상세 전용 구매 원칙에서 제외되어 있다.
## 3. Goals ## 3. Goals
@@ -32,6 +33,7 @@
- 홈 탭과 커뮤니티 탭의 리스트형 커뮤니티 아이템 외형을 하나의 컴포넌트로 통일한다. - 홈 탭과 커뮤니티 탭의 리스트형 커뮤니티 아이템 외형을 하나의 컴포넌트로 통일한다.
- 홈 탭 커뮤니티 아이템 우측 상단에 더보기를 제공한다. - 홈 탭 커뮤니티 아이템 우측 상단에 더보기를 제공한다.
- 커뮤니티 아이템 터치를 상세 진입으로 통일하고 구매는 상세 화면에서 처리한다. - 커뮤니티 아이템 터치를 상세 진입으로 통일하고 구매는 상세 화면에서 처리한다.
- 홈 공지 게시글도 본체·댓글·유료 잠금 영역을 상세 진입으로 통일한다.
- 유료 미구매 이미지 영역을 `Color.gray800`으로 통일한다. - 유료 미구매 이미지 영역을 `Color.gray800`으로 통일한다.
- 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar가 레이아웃 공간을 차지하지 않게 한다. - 하트·댓글을 모두 표시하지 않는 상태에서는 reaction bar가 레이아웃 공간을 차지하지 않게 한다.
@@ -57,7 +59,7 @@
## 6. 핵심 사용자 흐름 ## 6. 핵심 사용자 흐름
1. 사용자가 크리에이터 채널 홈의 커뮤니티 요약 아이템을 확인한다. 1. 사용자가 크리에이터 채널 홈의 커뮤니티 요약 또는 공지 아이템을 확인한다.
2. 아이템 본체를 터치하면 해당 `postId`의 커뮤니티 게시글 상세로 이동한다. 2. 아이템 본체를 터치하면 해당 `postId`의 커뮤니티 게시글 상세로 이동한다.
3. 유료 미구매 게시글이면 상세 화면에서 기존 구매 다이얼로그와 API를 사용한다. 3. 유료 미구매 게시글이면 상세 화면에서 기존 구매 다이얼로그와 API를 사용한다.
4. 사용자가 우측 상단 더보기를 터치하면 확정된 권한 규칙에 맞는 메뉴가 표시된다. 4. 사용자가 우측 상단 더보기를 터치하면 확정된 권한 규칙에 맞는 메뉴가 표시된다.
@@ -75,6 +77,7 @@
| `CCCI-007` | 확정 | 홈과 커뮤니티 탭의 리스트형·썸네일형 모두 목록 구매를 제거하고 유료 미구매 영역 터치를 상세 이동으로 처리한다. | 모든 목록 표시 모드에서 구매 다이얼로그가 열리지 않고 상세에서만 구매한다. | `P1-T2`, `P1-T3` | | `CCCI-007` | 확정 | 홈과 커뮤니티 탭의 리스트형·썸네일형 모두 목록 구매를 제거하고 유료 미구매 영역 터치를 상세 이동으로 처리한다. | 모든 목록 표시 모드에서 구매 다이얼로그가 열리지 않고 상세에서만 구매한다. | `P1-T2`, `P1-T3` |
| `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` |
## 8. UI/UX Expectations ## 8. UI/UX Expectations
@@ -104,6 +107,7 @@
- [x] 홈·리스트형·썸네일형·상세의 유료 미구매 영역이 모두 `Color.gray800`을 사용한다. - [x] 홈·리스트형·썸네일형·상세의 유료 미구매 영역이 모두 `Color.gray800`을 사용한다.
- [ ] 더보기·좋아요 터치가 상세 이동을 중복 발생시키지 않는다. - [ ] 더보기·좋아요 터치가 상세 이동을 중복 발생시키지 않는다.
- [x] 홈·커뮤니티 리스트와 상세의 유료 미구매 상태에서 하트·댓글용 빈 공간이 남지 않는다. - [x] 홈·커뮤니티 리스트와 상세의 유료 미구매 상태에서 하트·댓글용 빈 공간이 남지 않는다.
- [ ] 홈 공지의 본체·댓글·유료 잠금 영역이 상세로 이동하고 공지 카드에서 구매 다이얼로그가 직접 열리지 않는다.
- [x] `SodaLive-dev` 스킴 Debug 빌드가 성공한다. - [x] `SodaLive-dev` 스킴 Debug 빌드가 성공한다.
- [ ] 시뮬레이터에서 홈·커뮤니티 탭·상세의 아이템 터치, 더보기, 유료 잠금 색상을 확인한다. - [ ] 시뮬레이터에서 홈·커뮤니티 탭·상세의 아이템 터치, 더보기, 유료 잠금 색상을 확인한다.
@@ -130,6 +134,7 @@
| `CCCI-004`, `CCCI-005`, `CCCI-007` | 기존 목록·상세·구매 API 재사용 | `P1-T2`, `P1-T3` | 목록 구매 제거·상세 구매 연결·`Color.gray800` 참조 검증·Debug build | 목록/상세 잠금 영역·상세 진입·구매 확인 | | `CCCI-004`, `CCCI-005`, `CCCI-007` | 기존 목록·상세·구매 API 재사용 | `P1-T2`, `P1-T3` | 목록 구매 제거·상세 구매 연결·`Color.gray800` 참조 검증·Debug build | 목록/상세 잠금 영역·상세 진입·구매 확인 |
| `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 | 공지 본체·댓글·유료 잠금 상세 진입 확인 |
## 14. Decision Log ## 14. Decision Log
@@ -148,6 +153,7 @@
| 2026-08-10 | `DEC-011` | 확정 | 승인된 컴포넌트·데이터 흐름과 최종 동작·오류·검증 설계를 구현 기준으로 사용한다. | 사용자 최종 설계 승인 | `CCCI-001~008`, `EXT-001` | | 2026-08-10 | `DEC-011` | 확정 | 승인된 컴포넌트·데이터 흐름과 최종 동작·오류·검증 설계를 구현 기준으로 사용한다. | 사용자 최종 설계 승인 | `CCCI-001~008`, `EXT-001` |
| 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` |
## 15. 구현 설계 ## 15. 구현 설계
@@ -167,6 +173,7 @@
- 상세 진입 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를 유지한다.
- `CreatorChannelNoticeSection`은 본체·댓글·유료 잠금 영역에 하나의 상세 callback을 사용하고, `CreatorChannelHomeView`의 기존 `onTapCommunityDetail`을 전달한다. 공지 카드의 외형은 변경하지 않는다.
### 15.3 더보기와 변경 후 갱신 ### 15.3 더보기와 변경 후 갱신
@@ -189,6 +196,7 @@
- 대체 검증으로 홈 섹션·리스트형·썸네일형 Preview에서 유료 미구매·고정·댓글 비활성 상태를 구성한다. - 대체 검증으로 홈 섹션·리스트형·썸네일형 Preview에서 유료 미구매·고정·댓글 비활성 상태를 구성한다.
- 정적 검증은 `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`로 확인한다.
- 자동 검증은 `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`을 수동 확인한다.
@@ -206,3 +214,5 @@
- 2026-08-10: 사용 가능한 Simulator가 모두 `Shutdown`이고 `EXT-001` 반영 여부를 확인할 수 없어 실제 터치·메뉴·구매 통합 검증은 대기한다. - 2026-08-10: 사용 가능한 Simulator가 모두 `Shutdown`이고 `EXT-001` 반영 여부를 확인할 수 없어 실제 터치·메뉴·구매 통합 검증은 대기한다.
- 2026-08-10: 사용자 제보를 따라 목록·상세를 추적한 결과, 유료 잠금 상태에서도 빈 `reactionBar``.frame(height: 24)`가 유지되는 공통 원인을 확인하고 `CCCI-009`, `DEC-013`, `P2-R1`로 회귀 수정 범위를 확정했다. - 2026-08-10: 사용자 제보를 따라 목록·상세를 추적한 결과, 유료 잠금 상태에서도 빈 `reactionBar``.frame(height: 24)`가 유지되는 공통 원인을 확인하고 `CCCI-009`, `DEC-013`, `P2-R1`로 회귀 수정 범위를 확정했다.
- 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: 공지 callback을 단일 `onTapDetail`로 통일하고 잠금 영역의 직접 구매 `Button`을 제거한 뒤 `CreatorChannelHomeView.onTapCommunityDetail`에 연결했다. RED 계약 검사는 직접 구매 callback과 미연결 생성부를 각각 검출했고, GREEN은 기존 callback 0건·상세 연결 match, `git diff --check` 오류 0건, `SodaLive-dev` Debug build exit code 0을 확인했다.

View File

@@ -4,18 +4,18 @@
| 항목 | 내용 | | 항목 | 내용 |
|---|---| |---|---|
| 리뷰 대상 | Phase 1~2 / `P1-T1~P2-T2` | | 리뷰 대상 | Phase 1~2 / `P1-T1~P2-R2` |
| 기준 commit 또는 working tree | `89c070875cbeea8395c1e90cb9f47ff80df5d90f` 이후 미커밋 변경 | | 기준 commit 또는 working tree | `89c070875cbeea8395c1e90cb9f47ff80df5d90f` 이후 미커밋 변경 |
| 리뷰 일자 | 2026-08-10 | | 리뷰 일자 | 2026-08-10 |
| 리뷰어 | Codex 자가 리뷰—프로젝트 보수적 실행 정책에 따라 subagent 미사용 | | 리뷰어 | Codex 자가 리뷰—프로젝트 보수적 실행 정책에 따라 subagent 미사용 |
| 기준 문서 | `docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md`, `plan-task.md` | | 기준 문서 | `docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md`, `plan-task.md` |
| 리뷰 상태 | `REV-P3-003` 수정 완료·외부 통합 검증 대기 | | 리뷰 상태 | `REV-P3-004` 수정 완료·외부 통합 검증 대기 |
## 2. 리뷰 목적과 범위 ## 2. 리뷰 목적과 범위
### 목적 ### 목적
- 홈·커뮤니티 탭의 아이템, 상세 진입, 더보기, 목록 구매 제거, 상세 구매 이동이 `CCCI-001~008`을 충족하는지 확인한다. - 홈·커뮤니티 탭의 아이템과 공지, 상세 진입, 더보기, 목록 구매 제거, 상세 구매 이동이 `CCCI-001~010`을 충족하는지 확인한다.
- 코드·문서·검증 기록이 실제 working tree와 일치하는지 확인한다. - 코드·문서·검증 기록이 실제 working tree와 일치하는지 확인한다.
### 포함 범위 ### 포함 범위
@@ -50,8 +50,8 @@
### 문서와 코드 ### 문서와 코드
- 요구사항: `CCCI-001~009`, `DEC-001~013`, `EXT-001` - 요구사항: `CCCI-001~010`, `DEC-001~014`, `EXT-001`
- 계획: `P1-T1~P2-T2`, `P2-R1`, `P1-GATE`, `P2-GATE` - 계획: `P1-T1~P2-T2`, `P2-R1`, `P2-R2`, `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`
@@ -75,19 +75,21 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함
| 목록 구매 심볼·잠금 배경 `rg` contract check | 성공 | 목록 구매 심볼과 잠금 `Color.gray400` 0건, 목록·상세 `Color.gray800` match | | 목록 구매 심볼·잠금 배경 `rg` contract check | 성공 | 목록 구매 심볼과 잠금 `Color.gray400` 0건, 목록·상세 `Color.gray800` match |
| 상세 구매 `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 |
| `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`; 스킬 정책상 임의 부팅하지 않음 | | `xcrun simctl list devices` | 수동 검증 불가 | 모든 사용 가능한 Simulator가 `Shutdown`; 스킬 정책상 임의 부팅하지 않음 |
## 5. 발견 사항 요약 ## 5. 발견 사항 요약
사용자 회귀 제보로 확정한 reaction bar 빈 공간 1건은 수정·빌드를 완료했다. 사용자 회귀 제보로 확정한 reaction bar 빈 공간 1건은 수정·빌드를 완료했고, 홈 공지의 상세 전용 구매 원칙 누락 1건을 추가 확정했다.
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | | ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `REV-P3-001` | High | 수정 완료 | 상세 잠금 버튼이 구매 대신 공통 오류만 표시하던 계획 누락 | `P1-T3` | 완료 | | `REV-P3-001` | High | 수정 완료 | 상세 잠금 버튼이 구매 대신 공통 오류만 표시하던 계획 누락 | `P1-T3` | 완료 |
| `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` | 완료 |
## 6. 발견 사항 상세 ## 6. 발견 사항 상세
@@ -118,10 +120,20 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함
`CreatorChannelCommunityListItem``CreatorChannelCommunityPostDetailContentView`는 유료 잠금 상태에서 하트·댓글 자식만 숨기고, body에는 `reactionBar`를 항상 삽입했다. 그 결과 빈 `HStack``.frame(height: 24)`와 부모 `VStack` spacing이 홈·커뮤니티 리스트·상세에 남았다. 코드 수정 전에 `P2-R1`을 추가하고, 두 body에서 기존 `isPaidLocked`로 reaction bar 전체를 조건부 제외했다. focused contract check·`git diff --check`·Debug build가 통과했다. `CreatorChannelCommunityListItem``CreatorChannelCommunityPostDetailContentView`는 유료 잠금 상태에서 하트·댓글 자식만 숨기고, body에는 `reactionBar`를 항상 삽입했다. 그 결과 빈 `HStack``.frame(height: 24)`와 부모 `VStack` spacing이 홈·커뮤니티 리스트·상세에 남았다. 코드 수정 전에 `P2-R1`을 추가하고, 두 body에서 기존 `isPaidLocked`로 reaction bar 전체를 조건부 제외했다. focused contract check·`git diff --check`·Debug build가 통과했다.
### REV-P3-004 — 홈 공지의 상세 전용 구매 원칙 누락
- **심각도:** High
- **상태:** 수정 완료
- **관련 요구사항:** `CCCI-010`
- **소유 Task:** `P2-R2`
`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가 통과했다.
## 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-002`: 외부 조건이므로 `P3-GATE` 완료를 보류한다. - `REV-P3-002`: 외부 조건이므로 `P3-GATE` 완료를 보류한다.
## 8. 리뷰 종료 판정 ## 8. 리뷰 종료 판정
@@ -129,12 +141,12 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함
| 판정 항목 | 결과 | 근거 | | 판정 항목 | 결과 | 근거 |
|---|---|---| |---|---|---|
| 리뷰 범위 전체 확인 | 충족 | 사용자 회귀 제보와 수정 결과를 추가 반영 | | 리뷰 범위 전체 확인 | 충족 | 사용자 회귀 제보와 수정 결과를 추가 반영 |
| 후보 항목 판정 완료 | 충족 | `REV-P3-001`·`REV-P3-003` 수정 완료, `REV-P3-002` 보류 | | 후보 항목 판정 완료 | 충족 | `REV-P3-001`·`REV-P3-003`·`REV-P3-004` 수정 완료, `REV-P3-002` 보류 |
| 확정 항목 plan 반영 | 충족 | `P1-T3`, `P2-R1`, `DEC-012~013` | | 확정 항목 plan 반영 | 충족 | `P1-T3`, `P2-R1`, `P2-R2`, `DEC-012~014` |
| 보류 항목의 담당·재개 조건 기록 | 충족 | `EXT-001`·부팅된 Simulator | | 보류 항목의 담당·재개 조건 기록 | 충족 | `EXT-001`·부팅된 Simulator |
| 검증 명령과 결과 기록 | 충족 | 정적 계약·Debug build exit 0 | | 검증 명령과 결과 기록 | 충족 | 정적 계약·Debug build exit 0 |
**최종 결론:** iOS reaction bar 회귀 수정·빌드 완료, 외부 통합 검증 대기 **최종 결론:** 홈 공지 상세 구매 일원화 수정·빌드 완료, 외부 통합 검증 대기
**남은 항목:** 백엔드 `EXT-001` 반영 후 부팅된 Simulator에서 본체 상세 이동, 권한별 더보기, 목록 dialog 미노출, 상세 구매, `Color.gray800`을 확인한다. **남은 항목:** 백엔드 `EXT-001` 반영 후 부팅된 Simulator에서 본체 상세 이동, 권한별 더보기, 목록 dialog 미노출, 상세 구매, `Color.gray800`을 확인한다.
@@ -159,3 +171,15 @@ Backend: EXT-001 반영 여부를 이 환경에서 확인하지 못함
- `git diff --check` — 성공 - `git diff --check` — 성공
- `SodaLive-dev` 단일 Simulator Debug build — exit code 0 - `SodaLive-dev` 단일 Simulator Debug build — exit code 0
- 남은 항목: `REV-P3-002`와 Simulator 시각 검증 - 남은 항목: `REV-P3-002`와 Simulator 시각 검증
### 3차 수정 검증 — 2026-08-10
- 무엇을: `REV-P3-004`의 홈 공지 직접 구매·상세 미연결을 수정했다.
- 왜: 공지도 커뮤니티 게시글이므로 목록이 아닌 상세에서만 구매해야 한다.
- 어떻게:
- RED: 직접 구매 callback 7건, 홈 상세 callback 미연결 생성부 1건 확인
- GREEN: 기존 callback 0건, 단일 `onTapDetail`·홈 `onTapCommunityDetail` 연결 확인
- 기존 상세 구매 dialog/API와 구매 후 상세·홈·커뮤니티 refresh 연결 유지 확인
- `git diff --check` — 성공
- `SodaLive-dev` Debug build — exit code 0
- 남은 항목: `REV-P3-002`와 Simulator 통합 검증