Files

63 KiB

크리에이터 채널 커뮤니티 아이템 통일 구현 계획

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. 프로젝트 보수적 실행 모드에 따라 multi-agent는 사용하지 않는다.

Goal: 홈과 커뮤니티 탭의 커뮤니티 아이템 외형·상세 진입·더보기를 통일하고 구매는 상세에서만 처리하며, 유료 미구매 상태의 reaction·댓글·더보기 노출을 차단한다.

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.

Global Constraints

  • 기능 변경은 SodaLive/Sources/V2/CreatorChannel/**에서 해결하고 신규 dependency를 추가하지 않는다.
  • 기존 CommunityPostCard와 메인 홈 팔로잉·추천 사용처는 수정하지 않는다.
  • 백엔드는 홈 API의 notices·communities 아이템에 non-optional isCommentAvailable: Bool·isPinned: Bool을 제공한다.
  • 홈 API의 dateUtc는 유지하고 iOS에서 값 변환 없이 createdAtUtc로 연결한다.
  • 목록의 리스트형·썸네일형 구매 호출은 제거하고 기존 구매 dialog·API 호출을 상세로 옮긴다.
  • 유료 미구매 영역은 홈·리스트형·썸네일형·상세 모두 Color.gray800을 사용한다.
  • 하트·댓글을 모두 숨기는 유료 미구매 상태에서는 reaction bar의 고정 높이와 부모 spacing을 남기지 않는다.
  • 상세의 유료 미구매 상태에서는 댓글 구분선·목록·입력창을 표시하지 않고 작성·수정·삭제 API 요청을 시작하지 않는다.
  • 현재 실행 가능한 단일 test bundle이 없으므로 신규 test 타겟을 만들지 않고 정적 contract check·Preview·Debug build·시뮬레이터로 검증한다.
  • 사용자가 직접 요청하지 않았으므로 git commit은 실행하지 않는다.

문서 항목 내용
상태 P2-R3 상태별 수동 검증 완료·P2-R4 코드/자동 검증 완료·상태별 수동 확인 대기
작성일 2026-08-10
요구사항 기준 docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md
API 기준 GET /api/v2/creator-channels/{creatorId}/home 필드 확장·기존 커뮤니티 목록/상세 API
현재 Phase Phase 2
현재 활성 Goal P2-R4—더보기 상태별 수동 확인

목표

홈 요약·커뮤니티 리스트·썸네일에서 게시글 터치 결과와 잠금 스타일을 통일하고, 더보기 권한 메뉴와 상세 구매 흐름을 정확하게 유지한다. 상세의 유료 미구매 방문자에게는 reaction·댓글 영역을 표시하지 않고 댓글 mutation을 허용하지 않는다.

현재 상태

Phase 상태 완료 Task 활성/다음 Goal 차단 또는 남은 조건
1 완료 3/3 P1-GATE 완료 없음
2 진행 5/6 P2-R4 상태별 수동 확인
3 진행 0/1 P3-T1, P3-GATE EXT-001 반영

범위

포함

  • 홈 커뮤니티 응답 필드·Preview fixture 반영
  • 리스트형·썸네일형 목록 구매 제거와 상세 진입 통일
  • 홈 섹션의 CreatorChannelCommunityListItem 재사용
  • 홈의 고정·수정·삭제·신고 메뉴 연결과 성공 후 목록 동기화
  • 홈·커뮤니티 리스트와 상세에서 표시할 reaction이 없을 때 빈 공간 제거
  • 커뮤니티 상세의 유료 미구매 상태에서 댓글 구분선·목록·입력창 미표시와 작성·수정·삭제 mutation 차단
  • 정적 검사·Preview·Debug build·시뮬레이터 검증·문서 누적

제외

  • CommunityPostCard와 메인 홈 팔로잉·추천 섹션 변경
  • 구매 endpoint와 CommunityPostPurchaseDialog 내부 UI 변경
  • 커뮤니티 작성·상세·댓글의 범위 밖 리팩터링
  • 신규 공용 모델·protocol·factory·dependency·test target 추가
  • 백엔드 구현과 배포
  • 상세 API 댓글 payload 계약과 CreatorChannelReplyDetail/** 변경

기술적 제약

  • 홈 응답·탭 응답 모델은 통합하지 않고 CreatorChannelCommunitySection private 변환으로 연결한다.
  • 목록 구매 제거로 새로 미사용된 상태·메서드·dialog 분기만 제거한다.
  • 홈·커뮤니티 변경 성공 후 갱신은 CreatorChannelView의 한 helper에서 두 ViewModel 재조회를 호출한다.
  • 사용자 노출 오류는 기존 errorMessage·isShowPopup·toast 흐름을 유지한다.
  • 홈 응답에 추가 필드가 없으면 decoding 실패를 기존 홈 API 실패로 처리하고 fallback 값을 만들지 않는다.
  • 댓글 차단은 기존 유료 잠금 조건 price > 0 && existOrdered == false && isOwnPost == false와 상세의 isCommentAvailable을 함께 사용한다.

Task 검증 규칙

  • 현재 프로젝트에 실행 가능한 unit test bundle이 없어 SwiftUI View·Decodable 변경에 대한 신규 unit test를 추가하지 않는다.
  • 각 구현 Task는 변경 전 구조를 재현하는 실행 가능한 rg contract check를 RED로 실행한다.
  • 최소 구현 후 같은 contract check의 기대 결과가 반전되는지 확인하고, 영향 범위 xcodebuildGREEN을 판정한다.
  • Preview·시뮬레이터 검증은 시각·gesture 동작이 unit test target 없이 자동화되지 않는 부분의 대체 완료 증거로 사용한다.

Phase 1: 홈 계약과 목록 상호작용

Phase 결과: 홈 응답이 필요한 고정·댓글 상태를 디코딩하고, 커뮤니티 리스트형·썸네일형은 상세로 이동하며 구매 dialog·API는 상세에서 동작한다.

선행조건: PRD CCCI-004, CCCI-005, CCCI-007, CCCI-008EXT-001 계약 확정.

Phase 완료 조건: P1-T1, P1-T2, P1-T3, P1-GATE 완료.

Task 1.1 홈 커뮤니티 응답 계약 반영

Goal 실행 P1-T1: 홈 커뮤니티·공지 아이템이 isCommentAvailable·isPinned을 non-optional Bool로 디코딩한다.

  • 시작 조건: EXT-001 필드명·타입 확정.
  • 완료 증거: 모델·Preview fixture 반영, RED/GREEN contract check, SodaLive-dev Debug build.
  • 범위 밖: 백엔드 구현, 모델 통합, fallback 기본값.

Files:

  • Modify: SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift
  • Test: 신규 test 파일 없음—기존 Preview fixture·contract check·Debug build 사용

Interfaces:

  • Consumes: 홈 API communities·notices 아이템의 isCommentAvailable: Bool·isPinned: Bool.

  • Produces: CreatorChannelCommunityPostResponse.isCommentAvailable: Bool, CreatorChannelCommunityPostResponse.isPinned: Bool.

  • RED: 홈 응답 모델에 두 필드가 없음을 확인한다.

rg -n "let (isCommentAvailable|isPinned): Bool" SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift

Expected: match 0건, exit code 1.

  • RED 확인: 현재 Preview fixture가 두 필드 인자를 전달하지 않음을 확인한다.
rg -n "isCommentAvailable:|isPinned:" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift

Expected: match 0건, exit code 1.

  • GREEN: CreatorChannelCommunityPostResponse 마지막에 두 필드를 추가하고 세 개 Preview fixture에 명시적 샘플 값을 전달한다.
let commentCount: Int
let isLiked: Bool
let isCommentAvailable: Bool
let isPinned: Bool

Preview 규칙:

isCommentAvailable: index != 3,
isPinned: index == 1

CreatorChannelNoticeSection fixture는 각 아이템에 isCommentAvailable: true·isPinned: true/false를 명시한다.

  • GREEN 확인: 두 필드·세 fixture가 있고 Debug build가 성공함을 확인한다.
rg -n "let (isCommentAvailable|isPinned): Bool" SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.swift
rg -n "isCommentAvailable:|isPinned:" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelNoticeSection.swift
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build

Expected: 모델 2건·fixture 6건 이상 match, ** BUILD SUCCEEDED **.

  • REFACTOR: 추가 변환 타입·optional fallback·주석을 만들지 않고 git diff --check 결과를 누적한다.

Task 1.2 목록 구매 제거와 잠금 배경 통일

Goal 실행 P1-T2: 커뮤니티 리스트형·썸네일형의 유료 미구매 영역이 Color.gray800으로 보이고 터치 시 상세로 이동한다.

  • 시작 조건: P1-T1 완료.
  • 완료 증거: 목록 구매 심볼 0건, Color.gray400 잠금 배경 0건, 썸네일 유료 Preview, Debug build.
  • 범위 밖: 상세 구매 연결은 P1-T3에서 처리하고, 구매 endpoint·dialog 내부 UI는 변경하지 않는다.

Files:

  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityTabView.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityViewModel.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
  • Test: CreatorChannelCommunityGridItem_Previews in the grid item file·contract check·Debug build

Interfaces:

  • Consumes: 기존 onTapDetail, CreatorChannelCommunityPostDetailView 구매 흐름.

  • Produces: CreatorChannelCommunityListItem init without onTapPurchase, CreatorChannelCommunityGridItem init without onTapPurchase.

  • RED: 목록 구매 상태·callback·dialog가 남아 있음을 확인한다.

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

Expected: 기존 호출처·상태·메서드 match, exit code 0.

  • RED 확인: 리스트형·썸네일형 잠금 배경이 Color.gray400인 것을 확인한다.
rg -n "\.fill\(Color\.gray400\)|^[[:space:]]*Color\.gray400[[:space:]]*$" SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift

Expected: 잠금 배경 2건 match, exit code 0.

  • GREEN: 두 아이템의 onTapPurchase를 제거하고 잠금 영역을 상세 진입 gesture에 포함한다.
private var paidLockedImage: some View {
    RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)
        .fill(Color.gray800)
        .frame(maxWidth: .infinity)
        .frame(height: 236)
        .overlay(paidPriceOverlay)
}

Grid ZStack은 잠금/접근 가능 분기 밖에 하나의 gesture를 둔다.

.contentShape(Rectangle())
.onTapGesture(perform: onTapDetail)

썸네일 유료 잠금 상태를 고정된 fixture로 확인할 Preview를 추가한다.

struct CreatorChannelCommunityGridItem_Previews: PreviewProvider {
    static var previews: some View {
        CreatorChannelCommunityGridItem(
            post: CreatorChannelCommunityPostItem(
                postId: 1,
                creatorId: 10,
                creatorNickname: "크리에이터 이름",
                creatorProfileUrl: "https://picsum.photos/600",
                createdAtUtc: "2026-08-10T00:00:00Z",
                content: "유료 커뮤니티 게시글",
                imageUrl: nil,
                audioUrl: nil,
                price: 30,
                isCommentAvailable: true,
                likeCount: 5,
                commentCount: 2,
                isPinned: true,
                existOrdered: false,
                isLiked: false
            ),
            isOwnPost: false,
            onTapDetail: {}
        )
        .frame(width: 134, height: 134)
        .background(Color.black)
        .previewLayout(.sizeThatFits)
    }
}

CreatorChannelCommunityViewModel에서 isShowPostPurchaseView, selectedPostPrice, openPurchaseDialog, purchaseCommunityPost를 제거하고 CreatorChannelView.communityOverlay의 목록 구매 dialog 분기를 제거한다.

  • GREEN 확인: 목록 구매 심볼·Color.gray400이 사라지고 두 아이템에 Color.gray800이 있음을 확인한다.
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 "\.fill\(Color\.gray400\)|^[[:space:]]*Color\.gray400[[:space:]]*$" SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift
rg -n "Color\.gray800" SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build

Expected: 첫 rg와 잠금 배경 rg 각각 match 0건·exit code 1, 세 번째 rg 두 파일 match, ** BUILD SUCCEEDED **.

  • REFACTOR: 목록 구매 제거로 새로 미사용된 import·state만 정리하고 상세 구매 연결은 P1-T3에 남긴다.

Task 1.3 기존 구매 흐름을 상세로 이동

Goal 실행 P1-T3: 상세의 유료 미구매 잠금 영역에서 기존 구매 다이얼로그·API가 동작하고 성공 후 상세·홈·커뮤니티 목록을 갱신한다.

  • 시작 조건: P1-T2 완료.
  • 완료 증거: showPurchaseError 0건, 상세 구매 dialog·API 연결, Debug build.
  • 범위 밖: 구매 endpoint, CommunityPostPurchaseDialog 내부 UI, 결제 정책 변경.

Files:

  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift
  • Test: contract check·Debug build·시뮬레이터 상세 구매 확인

Interfaces:

  • Consumes: CreatorCommunityRepository.purchaseCommunityPost(postId:), CommunityPostPurchaseDialog, onCommunityRefresh.

  • Produces: isShowPurchaseDialog, presentPurchaseDialog(), purchaseCommunityPost().

  • RED: 상세 잠금 영역이 구매 대신 공통 오류만 표시하고 상세 구매 상태·메서드가 없음을 확인한다.

rg -n "showPurchaseError|onTapPurchase: showPurchaseError" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift
rg -n "isShowPurchaseDialog|CommunityPostPurchaseDialog|func (presentPurchaseDialog|purchaseCommunityPost)" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift

Expected: 첫 명령 match, 둘째 명령 match 0건·exit code 1.

  • GREEN: 상세 View에 기존 dialog를 표시하고 상세 ViewModel에 기존 구매 API 호출을 연결한다.
@Published var isShowPurchaseDialog = false

func presentPurchaseDialog() {
    guard let detail, detail.price > 0, detail.existOrdered == false, isOwnPost == false else { return }
    isShowPurchaseDialog = true
}

func purchaseCommunityPost()

구매 성공 분기는 fetchDetail(postId:)onCommunityRefresh?()를 호출한다. 상세 View는 onTapPurchase: viewModel.presentPurchaseDialog와 아래 기존 dialog를 연결한다.

CommunityPostPurchaseDialog(
    isShowing: $viewModel.isShowPurchaseDialog,
    can: detail.price,
    confirmAction: viewModel.purchaseCommunityPost
)
  • GREEN 확인: 오류 placeholder가 제거되고 상세 구매 연결·Debug build가 통과함을 확인한다.
rg -n "showPurchaseError" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift
rg -n "isShowPurchaseDialog|CommunityPostPurchaseDialog|func (presentPurchaseDialog|purchaseCommunityPost)|purchaseCommunityPost\(postId:" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build

Expected: 첫 rg match 0건·exit code 1, 둘째 rg 필수 연결 match, ** BUILD SUCCEEDED **.

  • REFACTOR: 목록 ViewModel에 구매 상태를 되돌리지 않고 상세의 기존 error/toast·refresh 흐름을 재사용한다.

Phase 1 Gate

Goal 실행 P1-GATE: 홈 계약과 목록 상호작용 변경의 컴파일·정적 계약을 판정한다.

git diff --check
rg -n "let (isCommentAvailable|isPinned): Bool" SodaLive/Sources/V2/CreatorChannel/Home/Models/CreatorChannelHomeResponse.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 "CommunityPostPurchaseDialog|func purchaseCommunityPost|purchaseCommunityPost\(postId:" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build

Expected: whitespace 오류 0건, 필드 2건, 세 UI의 Color.gray800·상세 구매 연결 match, ** BUILD SUCCEEDED **.

Phase 2: 홈 아이템 재사용과 메뉴 연결

Phase 결과: 홈이 기존 커뮤니티 리스트 아이템·상세 route·권한 메뉴를 재사용하고 변경 성공 후 홈·커뮤니티 목록을 동기화하며, 상세 유료 미구매 상태의 reaction·댓글 UI와 댓글 mutation을 차단한다.

선행조건: P1-GATE 완료.

Phase 완료 조건: P2-T1, P2-T2, P2-R1, P2-R2, P2-R3, P2-R4, P2-GATE 완료.

Task 2.1 홈 섹션에 기존 리스트 아이템·상세·더보기 연결

Goal 실행 P2-T1: 홈 섹션이 최대 3개의 CreatorChannelCommunityListItem을 표시하고 본체는 상세로, 더보기는 기존 권한 메뉴로 연결한다.

  • 시작 조건: P1-GATE 완료.
  • 완료 증거: CommunityPostCard 사용 0건, 로컬 변환·postId route·홈 overlay 연결, Preview, Debug build.
  • 범위 밖: 신규 표시 모델, 메뉴 UI 복제, 메인 홈 CommunityPostCard 수정.

Files:

  • Modify: SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/Home/CreatorChannelHomeView.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityTabView.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
  • Test: CreatorChannelCommunitySection_Previews·contract check·Debug build

Interfaces:

  • Consumes: CreatorChannelCommunityPostResponse, CreatorChannelCommunityPostItem, CreatorChannelCommunityViewModel.openReportMenu(post:), AppStep.creatorChannelCommunityPostDetail.

  • Produces: CreatorChannelCommunitySection(isOwnCreatorChannel:onTapMore:onTapDetail:), CreatorChannelHomeView(isOwnCreatorChannel:onTapCommunityMore:onTapCommunityDetail:), showCommunityPostDetail(_ postId: Int).

  • RED: 홈 섹션이 아직 CommunityPostCard를 사용하고 홈 상세·더보기 callback이 없음을 확인한다.

rg -n "CommunityPostCard\(" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift
rg -n "onTapCommunity(More|Detail)|communityPostItem" SodaLive/Sources/V2/CreatorChannel/Home SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift

Expected: 첫 명령 match 1건·exit code 0, 둘째 명령 match 0건·exit code 1.

  • RED 확인: communityOverlay 노출이 .community에만 한정됨을 확인한다.
rg -n "if viewModel\.selectedTab == \.community" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift

Expected: communityOverlay 함수 내 match 포함, exit code 0.

  • GREEN: 홈 게시글을 커뮤니티 탭 아이템으로 연결하는 private property를 추가한다.
private extension CreatorChannelCommunityPostResponse {
    var communityPostItem: CreatorChannelCommunityPostItem {
        CreatorChannelCommunityPostItem(
            postId: postId,
            creatorId: creatorId,
            creatorNickname: creatorNickname,
            creatorProfileUrl: creatorProfileUrl,
            createdAtUtc: dateUtc,
            content: content,
            imageUrl: imageUrl,
            audioUrl: audioUrl,
            price: price,
            isCommentAvailable: isCommentAvailable,
            likeCount: likeCount,
            commentCount: commentCount,
            isPinned: isPinned,
            existOrdered: existOrdered,
            isLiked: isLiked
        )
    }
}

CreatorChannelCommunitySectionisOwnCreatorChannel: Bool, onTapMore: (CreatorChannelCommunityPostItem) -> Void, onTapDetail: (Int) -> Void를 받고 CreatorChannelCommunityListItem에 연결한다. CreatorChannelHomeView는 동일 상태·callback을 전달한다.

홈 섹션 Preview도 변경된 입력 계약을 명시한다.

CreatorChannelCommunitySection(
    communities: previewCommunities,
    isOwnCreatorChannel: true,
    onSelectTab: { _ in },
    onTapMore: { _ in },
    onTapDetail: { _ in }
)
  • GREEN: 홈·커뮤니티 탭이 postId 기반 상세 helper를 공통 사용하고 홈에서도 기존 overlay를 보여준다.
private func showCommunityPostDetail(_ postId: Int) {
    AppState.shared.setAppStep(
        step: .creatorChannelCommunityPostDetail(
            postId: postId,
            onCommunityRefresh: {
                viewModel.fetchHome(creatorId: creatorId)
                communityViewModel.fetchFirstPage(creatorId: creatorId)
            }
        )
    )
}

CreatorChannelCommunityTabView.onTapDetail(Int) -> Void로 줄이고 post.postId를 전달한다. communityOverlay 조건은 .home || .community로 확장한다.

  • GREEN 확인: 기존 카드·탭 전용 상세 모델 callback이 제거되고 새 연결과 Debug build가 통과함을 확인한다.
rg -n "CommunityPostCard\(" SodaLive/Sources/V2/CreatorChannel/Home/Components/CreatorChannelCommunitySection.swift
rg -n "communityPostItem|CreatorChannelCommunityListItem\(|onTapCommunityMore|onTapCommunityDetail|showCommunityPostDetail\(_ postId: Int\)" SodaLive/Sources/V2/CreatorChannel/Home SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build

Expected: 첫 명령 match 0건·exit code 1, 둘째 명령 필수 연결 match, ** BUILD SUCCEEDED **.

  • REFACTOR: CreatorChannelCommunitySection.relativeTimeText 같은 카드 제거로 미사용된 헬퍼만 제거하고, CommunityPostCard의 다른 사용처는 건드리지 않는다.

Task 2.2 관리 동작 성공 후 홈·커뮤니티 동기화

Goal 실행 P2-T2: 홈 더보기에서 고정·수정·삭제한 결과가 홈과 커뮤니티 탭 목록에 모두 반영된다.

  • 시작 조건: P2-T1 완료.
  • 완료 증거: 한 개의 refreshCommunityFeeds() helper, 변경 성공 callback, Debug build.
  • 범위 밖: 좋아요 optimistic state, 신고 성공 후 목록 재조회, 메뉴 UI 변경.

Files:

  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityViewModel.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
  • Test: contract check·Debug build·시뮬레이터 관리 액션 확인

Interfaces:

  • Consumes: deleteCommunityPost, updateCommunityPostFixed, showCommunityModify, 두 ViewModel의 재조회 메서드.

  • Produces: deleteCommunityPost(onSuccess:), updateCommunityPostFixed(onSuccess:), refreshCommunityFeeds().

  • RED: 삭제·고정·수정 성공 후 커뮤니티 탭만 재조회하는 현재 구조를 확인한다.

rg -n "fetchFirstPage\(creatorId: creatorId\)|fetchHome\(creatorId: creatorId\)|deleteCommunityPost\(creatorId:|updateCommunityPostFixed\(creatorId:" SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityViewModel.swift SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift

Expected: ViewModel 내 재조회와 creatorId 기반 mutation, 수정 callback의 커뮤니티 탭 재조회 match.

  • RED 확인: refreshCommunityFeeds helper가 없음을 확인한다.
rg -n "refreshCommunityFeeds" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift

Expected: match 0건, exit code 1.

  • GREEN: ViewModel mutation은 성공 시 주입된 callback을 호출하고 재조회 소유권을 CreatorChannelView로 옮긴다.
func deleteCommunityPost(onSuccess: @escaping () -> Void)
func updateCommunityPostFixed(onSuccess: @escaping () -> Void)

private func refreshCommunityFeeds() {
    viewModel.fetchHome(creatorId: creatorId)
    communityViewModel.fetchFirstPage(creatorId: creatorId)
}

두 mutation의 기존 성공 분기에서 ViewModel 자체 재조회 대신 callback을 실행한다.

if decoded.success {
    self.errorMessage = I18n.Explorer.deleted
    self.isShowPopup = true
    onSuccess()
}
if decoded.success {
    onSuccess()
}

fixedAction, 삭제 확인 action, showCommunityModify 성공 callback이 refreshCommunityFeeds 하나를 사용하도록 연결한다.

  • GREEN 확인: helper가 두 재조회를 포함하고 mutation의 기존 creatorId 인자·ViewModel 내 재조회가 제거됨을 확인한다.
rg -n "refreshCommunityFeeds|fetchHome\(creatorId: creatorId\)|fetchFirstPage\(creatorId: creatorId\)" SodaLive/Sources/V2/CreatorChannel/CreatorChannelView.swift
rg -n "deleteCommunityPost\(creatorId:|updateCommunityPostFixed\(creatorId:|fetchFirstPage\(creatorId: creatorId\)" SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityViewModel.swift
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build

Expected: 첫 명령은 helper·두 재조회·세 action 연결 match, 둘째 명령 match 0건·exit code 1, ** BUILD SUCCEEDED **.

  • REFACTOR: 기존 글 작성 성공 callback은 요청 범위가 아니므로 별도 정리하지 않고 이번 변경의 중복만 제거한다.

Regression Task 2.3 표시할 reaction이 없을 때 빈 공간 제거

Goal 실행 P2-R1: 홈·커뮤니티 리스트와 상세의 유료 미구매 상태에서 하트·댓글과 reaction bar의 고정 높이를 함께 제거한다.

  • 리뷰 근거: REV-P3-003reviews/phase3-community-item-unification.md
  • 시작 조건: 사용자 회귀 제보와 코드 근거 확인, PRD CCCI-009·DEC-013 반영.
  • 완료 증거: 두 View의 무조건 reactionBar 삽입 0건, if !isPaidLocked 조건부 삽입 2건, git diff --check, Debug build.
  • 범위 밖: reaction icon·count 스타일 변경, 썸네일 레이아웃 변경, 신규 helper·test target 추가.

Files:

  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift
  • Modify: SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift
  • Test: 신규 test 파일 없음—focused contract check·Debug build 사용

Interfaces:

  • Consumes: 각 View의 기존 isPaidLocked, reactionBar.

  • Produces: 유료 잠금 상태에서 reactionBar가 View 계층에 없는 레이아웃.

  • RED: 두 View body가 reactionBar를 무조건 삽입하고, 내부의 빈 HStack.frame(height: 24)를 유지함을 확인한다.

rg -n '^[[:space:]]{12}reactionBar$' SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift
rg -n 'frame\(height: 24\)' SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift

Expected: 첫 명령 match 2건, 둘째 명령 match 2건.

  • GREEN: 두 body에서 reactionBarif !isPaidLocked일 때만 삽입하고, 내부의 중복 잠금 조건만 제거한다.

  • GREEN 확인: 무조건 삽입이 사라지고 조건부 삽입·Debug build가 통과함을 확인한다.

rg -n '^[[:space:]]{12}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
git diff --check
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build

Expected: 첫 명령 match 0건·exit code 1, 둘째 명령 match 2건, whitespace 오류 0건, ** BUILD SUCCEEDED **.

  • REFACTOR: 신규 상태·helper를 추가하지 않고 기존 isPaidLocked만 재사용한다.

Regression Task 2.4 공지 구매를 상세 흐름으로 일원화

Goal 실행 P2-R2: 홈 공지의 본체·댓글·유료 잠금 영역이 기존 커뮤니티 상세로 이동하고 구매는 상세에서만 시작한다.

  • 리뷰 근거: REV-P3-004reviews/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를 사용하는 동작.

  • RED: 공지에 별도 onTapPurchase·onTapNotice·onTapComment가 남고 홈 조립부가 상세 callback을 전달하지 않음을 확인한다.

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.

  • RED 확인: 상세 route와 상세 구매 성공 후 상세·홈·커뮤니티 refresh 흐름은 이미 존재함을 확인한다.
  • GREEN: 공지의 세 callback을 단일 onTapDetail로 교체하고 잠금 구매 Button wrapper를 제거한 뒤 홈의 onTapCommunityDetail을 전달한다.
  • GREEN 확인: 기존 공지 callback 0건, 단일 상세 callback·홈 연결 match, git diff --check, Debug build가 통과함을 확인한다.
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 **.

  • 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이 없는 상세 화면.

  • RED: 상세 View가 유료 잠금 여부 없이 isCommentAvailable만으로 댓글 목록·입력창을 표시하고 ViewModel의 댓글 전송 가능 여부도 같은 필드만 확인함을 재현한다.

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.

  • GREEN: ViewModel에 기존 유료 잠금 조건과 isCommentAvailable을 결합한 단일 댓글 상호작용 상태를 추가한다.
  • GREEN: 상세 View의 댓글 구분선·목록·하단 입력창은 해당 상태가 true일 때만 삽입한다.
  • GREEN: sendCommentconfirmDelete가 같은 상태를 guard하고, applyDetail은 잠금 상태의 댓글·편집·삭제 임시 상태를 기존 댓글 비활성 분기와 동일하게 비운다.
  • GREEN 확인: 댓글 표시 조건과 mutation guard가 단일 상태를 사용하고 기존 직접 조건이 상세 View에서 제거됐는지 확인한다.
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 **.

  • 수동 확인: 타인 게시글의 유료 미구매 상태에서는 reaction·댓글 구분선·목록·입력창이 보이지 않고 댓글 요청이 발생하지 않는다.
  • 수동 확인: 무료·구매 완료·본인 게시글에서는 기존 isCommentAvailable 값에 따라 댓글 UI와 동작이 유지된다.
  • 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.

  • RED: 방문자 분기가 구매 상태와 무관하게 else if !isOwnPost로 더보기를 삽입함을 확인한다.

  • GREEN: 방문자 분기를 else if !isPaidLocked로 변경해 기존 잠금 판정을 재사용한다.

  • GREEN 확인: 기존 조건 0건, 신규 조건 1건, git diff --check, Debug build를 확인한다.

  • 수동 확인: 홈·커뮤니티 탭에서 유료 미구매 타인은 숨김, 무료·구매 완료·작성자는 표시된다.

  • REFACTOR: 신규 상태·helper 없이 조건 한 줄만 변경하고 grid·공지·메뉴 callback은 수정하지 않는다.

Phase 2 Gate

Goal 실행 P2-GATE: 홈 재사용·상세·더보기·목록 동기화 구조를 판정한다.

git diff --check
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 -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건, 필수 연결·상세 댓글 조건/guard match, 홈 CommunityPostCard·공지 기존 callback match 0건·exit code 1, ** BUILD SUCCEEDED **.

Phase 3: 리뷰·통합 검증·문서 종결

Phase 결과: 정적 계약·Debug build·Preview·시뮬레이터 흐름을 확인하고 PRD·계획·리뷰 기록을 동기화한다.

선행조건: P2-R3와 재개한 P2-GATE 완료. 시뮬레이터 통합 판정은 EXT-001 반영 후 실행.

Phase 완료 조건: P3-T1, P3-GATE 완료·검증 기록 누적.

Task 3.1 영향 범위 리뷰와 완료 증거 누적

Goal 실행 P3-T1: 확정 요구사항 전체의 자동·수동 증거와 잔여 위험을 문서화한다.

  • 시작 조건: P2-GATE 완료.
  • 완료 증거: 리뷰 리포트, 정적 검사, Debug build, Preview/시뮬레이터 결과, PRD·계획 Progress.
  • 범위 밖: 실패와 무관한 리팩터링, test target 추가, 백엔드 수정.
  • TDD 예외 사유: production 동작을 추가하지 않는 리뷰·검증·문서 Task다.
  • 대체 검증 방법: 아래 정적 검사·Debug build·Preview·시뮬레이터 대조표를 사용한다.

Files:

  • Create: docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/reviews/phase3-community-item-unification.md
  • Modify: docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/prd.md
  • Modify: docs/20260810_크리에이터_채널_커뮤니티_아이템_통일/plan-task.md
  • Test: 정적 검사·Debug build·Preview·시뮬레이터 대체 검증

Interfaces:

  • Consumes: CCCI-001~012, EXT-001, Phase 1·2 산출물.

  • Produces: Phase 3 리뷰 판정, 최종 Progress, 남은 외부 조건.

  • 정적 검사로 필수 연결과 금지 호출처를 확인한다.

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 "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
git diff --check

Expected: 필드·홈 연결·세 UI의 Color.gray800 match, 목록 구매 심볼 0건·exit code 1, whitespace 오류 0건.

  • SodaLive-dev Debug build를 실행한다.
xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build

Expected: exit code 0, ** BUILD SUCCEEDED **.

  • Preview에서 홈 유료 잠금·고정·댓글 비활성 상태와 썸네일 유료 잠금 상태를 확인한다.
  • EXT-001 반영 후 시뮬레이터에서 홈·리스트형·썸네일형 본체 터치가 상세로 이동하고 목록 구매 dialog가 나오지 않음을 확인한다.
  • 본인 채널의 고정·수정·삭제, 타인 채널의 신고, 상세 구매, 더보기·좋아요 터치 중복 없음을 수동 확인한다.
  • 상세의 유료 미구매 상태에서 reaction·댓글 UI 미노출과 댓글 mutation 차단을 확인하고, 구매 후 isCommentAvailable에 따른 복원을 확인한다.
  • docs/sample/sample-review.md를 읽고 phase3-community-item-unification.md에 검토 범위·근거·명령·발견 사항을 기록한다.
  • 확정 수정 항목이 있으면 코드 수정 전 해당 Phase에 회귀 Task를 추가한다.
  • PRD 상태·요구사항 추적표와 이 문서의 체크박스·Progress를 실제 결과로 갱신한다.

Phase 3 Gate

Goal 실행 P3-GATE: 확정 요구사항·자동 검증·수동 검증·문서 추적성을 최종 판정한다.

  • P3-T1 리뷰에 확정 수정 항목이 없거나 추가 회귀 Task가 완료됐다.
  • git diff --check·정적 contract check·SodaLive-dev Debug build가 통과했다.
  • Preview 검증이 통과했다.
  • EXT-001 반영 후 시뮬레이터 통합 검증이 통과했다. 미반영이면 Phase를 완료 표시하지 않고 재개 조건을 Progress에 남긴다.
  • PRD·plan-task.md·review 리포트의 판정과 검증 기록이 일치한다.

실행 순서와 의존성

순서 Goal 선행조건 병행 가능 차단 시 다음 행동
1 P1-T1 PRD·EXT-001 계약 확정 아니요 백엔드 필드명·타입 확인
2 P1-T2 P1-T1 완료 아니요 목록 호출처·상세 구매 경계 재확인
3 P1-T3 P1-T2 완료 아니요 기존 상세 잠금·구매 API 경계 재확인
4 P1-GATE Phase 1 Task 전체 아니요 실패 소유 Task 수정
5 P2-T1 P1-GATE 아니요 홈·탭 모델 매핑 재확인
6 P2-T2 P2-T1 아니요 mutation 성공 callback 호출처 재확인
7 P2-R1 사용자 회귀 제보·PRD 반영 아니요 reaction bar 삽입 조건 재확인
8 P2-R2 사용자 후속 요청·PRD 반영 아니요 공지 상세 callback 경계 재확인
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 아니요 백엔드 반영 후 통합 검증 재개
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

변경 금지 항목

  • CommunityPostCard와 메인 홈 팔로잉·추천 사용처를 수정하지 않는다.
  • 구매 endpoint와 CommunityPostPurchaseDialog 내부 UI를 변경하지 않는다.
  • 홈 API의 dateUtc를 임의로 rename·parse·timezone 변환하지 않는다.
  • 백엔드 미반영 필드에 optional·fallback 값을 만들어 통합 실패를 숨기지 않는다.
  • 신규 protocol·factory·display model·dependency·test target을 추가하지 않는다.
  • 상세 API의 댓글 payload 계약과 CreatorChannelReplyDetail/**를 변경하지 않는다.
  • 요청 범위 밖의 기존 dead code·format·naming을 정리하지 않는다.

의사결정 및 중단 규칙

  • PRD·현재 API 응답·구현이 충돌하면 PRD의 EXT-001을 우선하고 추정 구현하지 않는다.
  • iOS 컴파일과 Preview 작업은 백엔드 반영 전에 진행할 수 있지만, 시뮬레이터 통합 판정은 EXT-001 반영 후에만 완료한다.
  • 구현 범위가 바뀌면 PRD Decision Log·요구사항·이 문서 체크박스를 코드 수정 전에 갱신한다.
  • 같은 차단 사유가 최초 시도와 자동 후속을 포함해 3회 연속 반복되고 독립 작업도 불가능할 때만 차단으로 판정한다.
  • 체크박스·검증·Progress가 모두 충족되기 전에 Goal·Phase를 완료 표시하지 않는다.

Progress

기존 기록을 삭제하지 않고 실제 실행 결과를 누적한다.

계획 작성 — 2026-08-10

  • 상태: 계획 검토 대기
  • 무엇을: 인터뷰·설계 승인 결과를 Phase 1~3·Task 6개·Gate 3개로 변환했고, 기준선 확인에서 누락된 상세 구매 이동 Task를 추가했다.
  • 왜: 코드 수정 전 파일 범위·인터페이스·검증·외부 의존을 확정하기 위해서다.
  • 어떻게:
    • rg로 현재 모델·callback·구매 상태·색상·Preview fixture 호출처를 확인했다.
    • docs/agent-guides/documentation-policy.md, build-test-verification.md, code-style.md, agent-execution-policy.md와 현재 PRD·샘플을 대조했다.
  • 남은 항목: 사용자 계획 검토, P1-T1 시작.
  • 다음 행동: 계획 승인 후 superpowers:executing-plansP1-T1을 실행한다.

Phase 1~2 구현 — 2026-08-10

  • 상태: 코드·정적 계약·Debug build 완료
  • 무엇을:
    • 홈 응답에 isCommentAvailable·isPinned을 추가했다.
    • 목록 구매를 제거하고 잠금 배경을 Color.gray800으로 통일했다.
    • 기준선에서 발견한 상세 구매 누락을 P1-T3로 추가한 뒤 기존 dialog·API를 상세로 이동했다.
    • 홈을 CreatorChannelCommunityListItem으로 교체하고 상세·더보기·두 목록 refresh를 연결했다.
  • 왜: CCCI-001~008의 iOS 코드 범위를 충족하기 위해서다.
  • 어떻게:
    • 각 Task의 RED/GREEN rg contract check를 실행했다.
    • iPhone 17 Pro iOS 26.0 대상 SodaLive-dev Debug build를 실행해 exit code 0을 확인했다.
  • 남은 항목: Preview·Simulator 통합 검증.
  • 다음 행동: Phase 3 리뷰와 외부 조건 확인.

Phase 3 리뷰 — 2026-08-10

  • 상태: 코드 리뷰 완료·통합 검증 대기
  • 무엇을: 전체 diff를 PRD·계획과 대조하고 reviews/phase3-community-item-unification.md를 작성했다.
  • 왜: 완료 주장을 코드·빌드·수동 증거로 분리하기 위해서다.
  • 어떻게:
    • 정적 계약·git diff --check·Debug build는 통과했다.
    • xcrun simctl list devices 결과 사용 가능한 Simulator가 모두 Shutdown임을 확인했다.
  • 남은 항목: EXT-001 반영, Preview, 본체 상세 이동·권한별 더보기·목록 dialog 미노출·상세 구매 수동 확인.
  • 다음 행동: 사용자가 Simulator를 부팅하고 백엔드 필드 반영을 알리면 P3-T1·P3-GATE를 재개한다.

Reaction bar 회귀 수정 — 2026-08-10

  • 상태: P2-R1 구현 전
  • 무엇을: 홈·커뮤니티 리스트와 상세의 하트·댓글이 모두 숨겨질 때 남는 빈 공간을 회귀 Task로 추가했다.
  • 왜: 두 View가 빈 reaction HStack의 고정 24pt 높이를 계속 배치하는 근본 원인을 확인했다.
  • 어떻게: 공용 리스트 아이템과 상세 content View의 body·reactionBar를 추적하고 REV-P3-003, CCCI-009, DEC-013을 연결했다.
  • 남은 항목: RED 확인, 최소 코드 수정, 정적 검사·Debug build, 문서 결과 누적.
  • 다음 행동: P2-R1을 실행한다.

Reaction bar 회귀 수정 완료 — 2026-08-10

  • 상태: P2-R1·P2-GATE 완료
  • 무엇을: 공용 리스트 아이템과 상세에서 유료 잠금 상태일 때 reactionBar 전체를 View 계층에서 제외했다.
  • 왜: 하트·댓글 자식만 숨기면 빈 HStack의 24pt 높이와 부모 spacing이 남기 때문이다.
  • 어떻게:
    • RED: body의 무조건 reactionBar 삽입 2건, .frame(height: 24) 2건을 확인했다.
    • GREEN: 무조건 삽입 0건, if !isPaidLocked 조건부 삽입 2건을 확인했다.
    • git diff --check 오류 0건, iPhone 17 Pro 대상 SodaLive-dev Debug build exit code 0을 확인했다.
  • 남은 항목: 기존 EXT-001·부팅된 Simulator 기반 Preview/통합 검증.
  • 다음 행동: 외부 조건 충족 후 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를 재개한다.

상세 유료 미구매 댓글 차단 계획 — 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-R4P2-GATE를 완료 판정한다.

Decision Log

날짜 ID 상태 결정 근거 영향 Goal/문서
2026-08-10 PLAN-DEC-001 확정 로컬 변환·기존 컴포넌트·기존 메뉴 상태를 재사용한다. PRD DEC-008~011 P2-T1, P2-T2
2026-08-10 PLAN-DEC-002 확정 목록 구매를 홈·리스트형·썸네일형에서 모두 제거한다. PRD DEC-003, DEC-007 P1-T2
2026-08-10 PLAN-DEC-003 확정 백엔드 필드는 non-optional로 디코딩하고 미반영 응답을 fallback으로 숨기지 않는다. PRD DEC-006, EXT-001 P1-T1, P3-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-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

발견된 문제

ID 심각도 상태 발견 내용 영향 Goal 처리 계획
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-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로 제한했다. 홈·커뮤니티 리스트 상태를 수동 검증한다.

최종 보고 형식

구현 결과: 홈·커뮤니티 아이템 통일과 상세 구매·댓글 잠금 일원화

- 변경: 홈 아이템 재사용, 목록 구매 제거, 더보기·상세·잠금 색상 통일, 상세 유료 미구매 댓글 차단
- 결정: `DEC-001~015`, `PLAN-DEC-001~008`
- 검증:
  - 정적 contract check·`git diff --check` — 실제 결과
  - `SodaLive-dev` Debug build — 실제 결과
  - Preview·시뮬레이터 — 실제 결과 또는 외부 조건
- 남은 항목: `EXT-001` 반영 여부 또는 없음
- 문서: PRD·`plan-task.md`·Phase 3 review 리포트