# 커뮤니티 게시글 상세 구현 계획 > **For agentic workers:** PRD를 기준으로 task 단위로 진행한다. 구현 중 요구사항 변경이 생기면 먼저 `docs/20260707_커뮤니티_게시글_상세/prd.md`를 보강하고 이 문서의 체크리스트를 갱신한다. **Goal:** V2 커뮤니티 게시글 상세 화면을 추가하고, 상세/댓글/답글 조회 API와 기존 커뮤니티 mutation API를 연결한다. **Architecture:** 상세/댓글/답글 조회는 `SodaLive/Sources/V2/CreatorChannel/Community/**`의 V2 API/Repository에 추가한다. 상세 화면 상태는 신규 `Community/Detail` ViewModel이 관리하고, 댓글 작성/수정/삭제/좋아요 mutation은 기존 `CreatorCommunityRepository`를 재사용한다. 댓글의 답글 상세 화면은 기존 `CreatorChannelReplyDetailView` 계열을 복사하지 않고 context/display adapter를 확장해 재사용한다. **Tech Stack:** SwiftUI, Combine, Moya, Kingfisher/SDWebImageSwiftUI, 기존 `AppStep`/`AppState`, 기존 V2 design token(`Color`, `SodaSpacing`, `appFont`) --- ## 기준 문서 - PRD: `docs/20260707_커뮤니티_게시글_상세/prd.md` - 문서 정책: `docs/agent-guides/documentation-policy.md` - 빌드/검증: `docs/agent-guides/build-test-verification.md` - 커뮤니티 탭 PRD: `docs/20260705_크리에이터_채널_커뮤니티_탭/prd.md` - 글 상세 - 답글 쓰기 PRD: `docs/20260707_글_상세_답글_쓰기/prd.md` - Figma 댓글 가능 상세: `290:9243`, `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9243&m=dev` - Figma 댓글 불가능 상세: `290:9266`, `https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=290-9266&m=dev` ## 구현 원칙 - 신규 상세 화면 파일은 `SodaLive/Sources/V2/CreatorChannel/Community/Detail/**` 아래에 둔다. - 신규 조회 모델은 `SodaLive/Sources/V2/CreatorChannel/Community/Models/**` 아래에 둔다. - 기존 `CreatorChannelCommunityApi.getCommunity`의 `page=0` 정책을 유지하고, 신규 V2 조회 API에도 `page - 1` 보정을 넣지 않는다. - 댓글 작성/답글 작성/댓글 수정/댓글 삭제/좋아요 API는 기존 `CreatorCommunityRepository`를 사용한다. - 댓글 불가능 게시글은 좋아요 UI는 유지하고, 댓글 icon/count, 댓글 목록, 하단 댓글 입력 바를 숨긴다. - 댓글 입력창은 3줄까지 자동 확장하고 4줄 이상은 내부 스크롤로 처리한다. - 전송 버튼 icon은 `ic_arrow_up_gray`, `ic_arrow_up_white`를 사용한다. - 댓글 쓰기 UI에는 기존 비밀댓글 toggle을 추가하고, 기존 `isShowSecret` 노출 정책을 따른다. - 답글 상세 UI는 기존 `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/**` 구현을 재사용한다. ## 주요 대상 파일 ### 생성 - `SodaLive/Sources/V2/CreatorChannel/Community/Models/CreatorChannelCommunityPostDetailResponse.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentListView.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentRow.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailInputBar.swift` ### 수정 - `SodaLive/Sources/V2/CreatorChannel/Community/Repository/CreatorChannelCommunityApi.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/Repository/CreatorChannelCommunityRepository.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityTabView.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift` - `SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift` - `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Models/CreatorChannelReplyDetailContext.swift` - `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Models/CreatorChannelReplyDetailDisplayItem.swift` - `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailViewModel.swift` - `SodaLive/Sources/App/AppStep.swift` - `SodaLive/Sources/ContentView.swift` - `SodaLive/Sources/I18n/I18n.swift` - `SodaLive.xcodeproj/project.pbxproj` ### 확인/재사용 - `SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift` - `SodaLive/Sources/V2/Component/Card/CommunityPostCard.swift` - `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift` - `SodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkListItem.swift` - `SodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityRepository.swift` - `SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/ModifyCommunityPostCommentRequest.swift` ## TASK 체크리스트 ### Phase 1: 조회 모델과 API - [x] **Task 1.1: 커뮤니티 게시글 상세 응답 모델 생성** - 대상 파일: - 생성: `SodaLive/Sources/V2/CreatorChannel/Community/Models/CreatorChannelCommunityPostDetailResponse.swift` - 작업 내용: - `CreatorChannelCommunityPostDetailResponse`를 `Decodable`, `Identifiable`로 정의한다. - `CreatorChannelCommunityCommentsResponse`, `CreatorChannelCommunityCommentResponse`, `CreatorChannelCommunityReplyResponse`, `CreatorChannelCommunityRepliesResponse`를 정의한다. - Kotlin `Long` 필드는 Swift `Int`로 선언한다. - `CreatorChannelCommunityCommentResponse`와 `CreatorChannelCommunityReplyResponse`에는 `writerId: Int`를 포함한다. - 각 시간 필드는 `DateParser.relativeTimeText(fromUTC:fallback:now:)`를 사용하는 helper를 제공한다. - 검증 기준: - 실행 명령: `rg "struct CreatorChannelCommunityPostDetailResponse|struct CreatorChannelCommunityCommentsResponse|struct CreatorChannelCommunityCommentResponse|writerId|struct CreatorChannelCommunityRepliesResponse|relativeTimeText" SodaLive/Sources/V2/CreatorChannel/Community/Models/CreatorChannelCommunityPostDetailResponse.swift` - 기대 결과: 상세/댓글/답글 모델과 `writerId`, 상대 시간 helper가 확인된다. - [x] **Task 1.2: 커뮤니티 게시글 상세/댓글/답글 조회 API 추가** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Repository/CreatorChannelCommunityApi.swift` - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Repository/CreatorChannelCommunityRepository.swift` - 작업 내용: - `CreatorChannelCommunityApi`에 `getCommunityPostDetail(postId:)`, `getCommunityPostComments(postId:page:size:)`, `getCommunityCommentReplies(commentId:page:size:)` case를 추가한다. - path는 각각 `/api/v2/creator-channels/community-posts/{postId}`, `/api/v2/creator-channels/community-posts/{postId}/comments`, `/api/v2/creator-channels/community-comments/{commentId}/replies`로 구성한다. - 댓글/답글 query는 `page`, `size`를 그대로 전달하고 `page - 1` 보정을 하지 않는다. - Repository에 동일 이름의 publisher 함수를 추가한다. - 검증 기준: - 실행 명령: `rg "getCommunityPostDetail|getCommunityPostComments|getCommunityCommentReplies|community-posts|community-comments|URLEncoding.queryString" SodaLive/Sources/V2/CreatorChannel/Community/Repository` - 기대 결과: 신규 endpoint 3개와 query 전달이 확인된다. ### Phase 2: 라우팅과 목록 진입 - [x] **Task 2.1: 게시글 상세 route 추가** - 대상 파일: - 수정: `SodaLive/Sources/App/AppStep.swift` - 수정: `SodaLive/Sources/ContentView.swift` - 작업 내용: - `AppStep`에 `creatorChannelCommunityPostDetail(postId:onCommunityRefresh:)` case를 추가한다. - `ContentView` switch에서 `CreatorChannelCommunityPostDetailView(postId:onCommunityRefresh:)`를 반환한다. - `onCommunityRefresh`는 상세 mutation 성공 후 커뮤니티 탭 첫 페이지를 갱신하기 위한 optional callback으로 둔다. - 검증 기준: - 실행 명령: `rg "creatorChannelCommunityPostDetail|CreatorChannelCommunityPostDetailView|onCommunityRefresh" SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift` - 기대 결과: 새 route와 화면 연결이 확인된다. - [x] **Task 2.2: 커뮤니티 리스트/그리드 item tap에서 상세 진입 연결** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityTabView.swift` - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swift` - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swift` - 작업 내용: - 리스트 item과 그리드 item에 `onTapDetail` closure를 추가한다. - item 전체 tap은 상세 route로 연결한다. - 좋아요, 댓글, 더보기, 구매, 오디오 재생 버튼 tap은 상세 진입과 충돌하지 않게 button action으로 유지한다. - 상세 route에는 `post.postId`와 `viewModel.fetchFirstPage(creatorId:)` callback을 전달한다. - 검증 기준: - 실행 명령: `rg "onTapDetail|creatorChannelCommunityPostDetail|fetchFirstPage\\(creatorId: creatorId\\)|contentShape|Button" SodaLive/Sources/V2/CreatorChannel/Community` - 기대 결과: 리스트/그리드 item tap 상세 진입과 내부 버튼 action 분리가 확인된다. ### Phase 3: 상세 ViewModel과 mutation 재사용 - [x] **Task 3.1: 상세 ViewModel 기본 상태와 상세 조회 구현** - 대상 파일: - 생성: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - 작업 내용: - 상태로 `detail`, `comments`, `commentText`, `isSecret`, `isLoading`, `isLoadingNextPage`, `hasLoaded`, `errorMessage`, `isShowPopup`, `selectedComment`, `editingComment`, `deletingComment`를 둔다. - `fetchDetail(postId:)`에서 상세 API를 호출하고 게시글과 초기 댓글 목록을 구성한다. - `isCommentAvailable == false`이면 댓글 입력/목록 상태를 비활성 상태로 유지한다. - `isShowSecret` computed property는 기존 정책인 `price > 0 && existOrdered && creatorId != UserDefaults.int(forKey: .userId)`를 따른다. - `isCommentSendEnabled`는 trim된 `commentText`가 비어 있지 않고 로딩 중이 아닐 때 true로 계산한다. - 검증 기준: - 실행 명령: `rg "final class CreatorChannelCommunityPostDetailViewModel|fetchDetail|detail|comments|commentText|isSecret|isShowSecret|isCommentSendEnabled|getCommunityPostDetail" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - 기대 결과: 상세 조회와 댓글 입력 상태가 확인된다. - [x] **Task 3.2: 댓글 페이지네이션 구현** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - 작업 내용: - `fetchNextCommentsIfNeeded(currentComment:)`를 추가한다. - 마지막 댓글 노출 시 `hasNext == true`이면 `page + 1`로 댓글 API를 호출한다. - 중복 호출을 막기 위해 `isLoadingNextPage`와 `isLoading` guard를 둔다. - 첫 페이지 재조회 시 기존 댓글 목록을 교체하고, 다음 페이지 성공 시 append한다. - 검증 기준: - 실행 명령: `rg "fetchNextCommentsIfNeeded|getCommunityPostComments|isLoadingNextPage|hasNext|page \\+ 1|append\\(contentsOf:" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - 기대 결과: 댓글 추가 조회와 append 로직이 확인된다. - [x] **Task 3.3: 좋아요 mutation과 optimistic state 구현** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - 작업 내용: - `toggleLike()`에서 기존 `CreatorCommunityRepository.communityPostLike(postId:)`를 호출한다. - tap 직후 `isLiked`와 `likeCount`를 local state에 반영한다. - 실패 시 toast를 표시하고 tap 직전의 `isLiked`, `likeCount` 값으로 local state를 복구한다. - 검증 기준: - 실행 명령: `rg "toggleLike|communityPostLike|isLiked|likeCount|errorMessage|isShowPopup" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - 기대 결과: 기존 좋아요 API와 local state 반영이 확인된다. - [x] **Task 3.4: 댓글 작성/수정/삭제 mutation 구현** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - 작업 내용: - 신규 댓글 작성은 `CreatorCommunityRepository.createCommunityPostComment(comment:postId:parentId:isSecret:)`를 사용하고 `parentId == nil`, `isSecret == 비밀댓글 toggle 값`으로 호출한다. - 댓글 수정은 `ModifyCommunityPostCommentRequest(commentId:)`에 수정 content를 담아 `modifyComment(request:)`를 호출한다. - 댓글 삭제는 `ModifyCommunityPostCommentRequest(commentId:)`에 `isActive = false`를 담아 `modifyComment(request:)`를 호출한다. - 성공 후 입력값과 수정/삭제 선택 상태를 초기화하고 상세 또는 댓글 첫 페이지를 재조회한다. - mutation 성공 시 `onCommunityRefresh` callback을 호출해 목록 갱신을 예약한다. - 검증 기준: - 실행 명령: `rg "createCommunityPostComment|parentId: nil|isSecret|ModifyCommunityPostCommentRequest|modifyComment|isActive = false|onCommunityRefresh|commentText = \"\"" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - 기대 결과: 기존 커뮤니티 댓글 mutation API 재사용이 확인된다. ### Phase 4: 상세 UI 컴포넌트 - [x] **Task 4.1: 상세 게시글 콘텐츠 컴포넌트 구현** - 대상 파일: - 생성: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift` - 작업 내용: - Figma 기준으로 작성자 프로필, 닉네임, 상대 시간, 본문, 이미지/잠금/오디오 영역, 좋아요/댓글 reaction을 표시한다. - 유료 미구매 상태는 `price > 0 && existOrdered == false && isOwnPost == false`로 판정한다. - 유료 미구매 상태에서는 `gray800` 계열 이미지 영역, 잠금 아이콘, 캐시 가격 capsule을 표시한다. - 구매 완료 상태는 `구매완료` capsule tag를 표시한다. - 접근 가능한 이미지/GIF는 기존 `WebImage` 또는 `DownsampledKFImage` 패턴을 따른다. - `audioUrl`이 있으면 기존 `CreatorCommunityMediaPlayerManager`와 `ContentPlayManager` 흐름을 재사용한다. - `isCommentAvailable == false`이면 댓글 icon/count만 숨기고 좋아요는 유지한다. - 검증 기준: - 실행 명령: `rg "CreatorChannelCommunityPostDetailContentView|ic_feed_community_heart_fill|ic_feed_community_heart|ic_feed_community_reply|ic_new_community_lock|ic_bar_cash|구매완료|CreatorCommunityMediaPlayerManager|isCommentAvailable" SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift` - 기대 결과: 게시글 상세 표시, 좋아요, 댓글 숨김, 잠금/구매완료 UI가 확인된다. - [x] **Task 4.2: 댓글 row와 최신 답글 카드 구현** - 대상 파일: - 생성: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentRow.swift` - 작업 내용: - 댓글 row는 42pt 프로필, 닉네임, 상대 시간, 본문, 우측 더보기 버튼을 표시한다. - `writerId == UserDefaults.int(forKey: .userId)`로 내 댓글 여부를 판정한다. - `post.creatorId == UserDefaults.int(forKey: .userId)`로 현재 사용자가 해당 게시글을 쓴 크리에이터인지 판정한다. - `latestReply != nil`이면 20pt 프로필, 닉네임, 상대 시간, 본문을 가진 `Color.gray900`, radius 14pt 답글 카드를 표시한다. - 최신 답글이 있으면 기존 팬Talk/ReplyDetail connector와 같은 좌측 연결선을 표시한다. - row tap은 답글 상세 진입 action을 호출한다. - 더보기 버튼 tap은 popup/action 선택으로 연결하고 row tap과 충돌하지 않게 한다. - 검증 기준: - 실행 명령: `rg "CreatorChannelCommunityPostDetailCommentRow|latestReply|writerId|creatorId|UserDefaults.int|Color\\.gray900|RoundedRectangle|Shape|Path|onTapReplyDetail|onTapMore" SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentRow.swift` - 기대 결과: 댓글 row, 최신 답글 카드, 연결선, action 분리가 확인된다. - [x] **Task 4.3: 댓글 목록 컴포넌트 구현** - 대상 파일: - 생성: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentListView.swift` - 작업 내용: - section header는 `댓글 {commentCount}`를 표시한다. - 댓글 목록은 `LazyVStack`으로 표시하고 마지막 댓글 `onAppear`에서 다음 페이지 조회를 요청한다. - 댓글이 비어 있으면 header만 유지하거나 Figma 기준 빈 목록 영역을 유지한다. - 선택된 댓글의 popup은 기존 `CreatorChannelActionPopup` 또는 `CreatorChannelReplyDetailActionPopup` 기반으로 수정/삭제 action을 표시한다. - 검증 기준: - 실행 명령: `rg "CreatorChannelCommunityPostDetailCommentListView|댓글|commentCount|LazyVStack|onAppear|fetchNext|CreatorChannelActionPopup|onTapEdit|onTapDelete" SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentListView.swift` - 기대 결과: 댓글 목록, 페이지네이션 trigger, popup action 연결이 확인된다. - [x] **Task 4.4: 하단 댓글 입력 바 구현** - 대상 파일: - 생성: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailInputBar.swift` - 수정: `SodaLive/Sources/I18n/I18n.swift` - 작업 내용: - placeholder는 `댓글을 입력하세요`를 사용한다. - 기존 비밀댓글 toggle UI를 추가하고 `isShowSecret`일 때만 표시한다. - 입력창은 1줄에서 3줄까지 자동 확장한다. - 4줄 이상은 3줄 높이로 유지하고 내부 vertical scroll을 허용한다. - 전송 버튼은 비활성 상태에서 `ic_arrow_up_gray`/`gray900`, 활성 상태에서 `ic_arrow_up_white`/`soda400`를 사용한다. - 수정 모드에서는 취소 버튼 또는 기존 입력 복귀 UX를 최소 범위로 제공한다. - 검증 기준: - 실행 명령: `rg "CreatorChannelCommunityPostDetailInputBar|댓글을 입력하세요|isShowSecret|isSecret|ic_arrow_up_gray|ic_arrow_up_white|Color\\.gray900|Color\\.soda400|axis: \\.vertical|lineLimit" SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailInputBar.swift SodaLive/Sources/I18n/I18n.swift` - 기대 결과: placeholder, 비밀댓글 toggle, 3줄 확장, 전송 버튼 상태가 확인된다. - [x] **Task 4.5: 상세 화면 조립** - 대상 파일: - 생성: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift` - 작업 내용: - `CreatorChannelReplyDetailTitleBar`를 재사용해 back-only title bar를 표시한다. - `ScrollView` 안에 게시글 콘텐츠와 댓글 목록을 배치한다. - `isCommentAvailable == true`일 때만 하단 입력 바를 `safeAreaInset(edge: .bottom)`으로 고정한다. - loading, toast, 삭제 confirm modal을 연결한다. - `onAppear`에서 `viewModel.fetchDetail(postId:)`를 호출한다. - 검증 기준: - 실행 명령: `rg "CreatorChannelCommunityPostDetailView|CreatorChannelReplyDetailTitleBar|CreatorChannelCommunityPostDetailContentView|CreatorChannelCommunityPostDetailCommentListView|CreatorChannelCommunityPostDetailInputBar|safeAreaInset|fetchDetail|SodaV2ActionModal|sodaToast" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift` - 기대 결과: 상세 화면 조립과 댓글 가능 여부별 하단 입력 바 조건이 확인된다. ### Phase 5: 답글 상세 재사용 확장 - [x] **Task 5.1: 신규 댓글 응답을 답글 상세 context로 전달할 수 있게 확장** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Models/CreatorChannelReplyDetailContext.swift` - 수정: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/Models/CreatorChannelReplyDetailDisplayItem.swift` - 수정: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailViewModel.swift` - 작업 내용: - 기존 `.community(creatorId:postId:parentComment: GetCommunityPostCommentListItem)` 경로는 유지한다. - 신규 상세 댓글 응답을 전달할 수 있는 context case 또는 adapter를 추가한다. - `CreatorChannelCommunityCommentResponse`는 `commentId`, `writerId`, `writerNickname`, `writerProfileImageUrl`, `content`, `createdAtUtc`로 display item을 만든다. - 신규 응답에는 `isSecret`이 없으므로 원댓글 표시용 `isSecret`은 false로 처리하고, 신규 댓글 입력의 비밀 여부는 상세 댓글 작성 UI에서만 다룬다. - 검증 기준: - 실행 명령: `rg "CreatorChannelCommunityCommentResponse|communityPostDetail|commentId|writerId|writerProfileImageUrl|DateParser.relativeTimeText|isSecret: false" SodaLive/Sources/V2/CreatorChannel/ReplyDetail` - 기대 결과: 신규 댓글 응답에서 답글 상세 display item을 만들 수 있다. - [x] **Task 5.2: 답글 상세에서 신규 답글 조회 API 사용** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailViewModel.swift` - 작업 내용: - 신규 상세 댓글 context에서는 `CreatorChannelCommunityRepository.getCommunityCommentReplies(commentId:page:size:)`를 사용한다. - 기존 V1 댓글 context는 기존 `CreatorCommunityRepository.getCommentReplyList(commentId:page:size:)` 경로를 유지한다. - 신규 `CreatorChannelCommunityReplyResponse`를 `CreatorChannelReplyDetailDisplayItem`으로 변환한다. - `hasNext`, `page`, `size`를 사용해 답글 페이지네이션을 처리한다. - 검증 기준: - 실행 명령: `rg "getCommunityCommentReplies|CreatorChannelCommunityRepliesResponse|CreatorChannelCommunityReplyResponse|hasNext|communityReplyPage|fetchNextCommunityRepliesIfNeeded" SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailViewModel.swift` - 기대 결과: 신규 답글 조회 API와 기존 V1 답글 조회 경로가 분리되어 확인된다. - [x] **Task 5.3: 댓글 row tap에서 답글 상세 route 연결** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift` - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentRow.swift` - 수정: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swift` - 수정: `SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailViewModel.swift` - 수정: `SodaLive/Sources/App/AppStep.swift` - 수정: `SodaLive/Sources/ContentView.swift` - 작업 내용: - 댓글 row tap 시 `AppState.shared.setAppStep(step: .creatorChannelReplyDetail(...))`을 호출한다. - context에는 `creatorId`, `postId`, 선택 댓글 응답을 전달한다. - `CreatorChannelReplyDetailView`에서 커뮤니티 답글 mutation 성공 시 호출할 `onCommunityRefresh` callback을 추가하고, 게시글 상세 진입 시 `viewModel.fetchDetail(postId:)`를 전달한다. - 검증 기준: - 실행 명령: `rg "creatorChannelReplyDetail|CreatorChannelReplyDetailContext|CreatorChannelCommunityCommentResponse|onTapReplyDetail|onCommunityRefresh|postId|creatorId" SodaLive/Sources/V2/CreatorChannel/Community/Detail SodaLive/Sources/V2/CreatorChannel/ReplyDetail SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift` - 기대 결과: 상세 댓글에서 기존 답글 상세 화면으로 진입하는 route가 확인된다. ### Phase 6: 권한, 문구, 프로젝트 등록 - [x] **Task 6.1: 댓글 popup 권한과 수정/삭제 모드 연결, 신고 차단 기록** - 대상 파일: - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift` - 수정: `SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentListView.swift` - 작업 내용: - 내 댓글/답글이면 `수정하기`, `삭제하기`를 표시한다. - 내가 해당 커뮤니티 게시글을 쓴 크리에이터이면 내 댓글/답글이 아니어도 `삭제하기`를 표시한다. - `수정하기` tap 시 하단 입력창에 기존 댓글 내용을 채우고 send 시 수정 API를 호출한다. - `삭제하기` tap 시 `SodaV2ActionModal`로 삭제 확인을 표시한다. - [blocked] 신고는 기존 `ReportRequest`/`ReportType`에 커뮤니티 댓글 신고 식별자가 없고, 기존 `CreatorCommunityCommentItemView`도 신고 action을 노출하지 않아 연결하지 않는다. 백엔드/API 계약에 community comment report identifier가 추가된 뒤 별도 구현한다. - 검증 기준: - 실행 명령: `rg "writerId ==|creatorId ==|currentUserId|canEdit|canDelete|beginEditing|editingComment|deletingComment|SodaV2ActionModal|confirmDelete|CreatorChannelActionPopup" SodaLive/Sources/V2/CreatorChannel/Community/Detail` - 기대 결과: 작성자 수정/삭제 권한, 게시글 크리에이터 삭제 권한, 수정 모드가 확인된다. 신고 action은 community comment report identifier API 계약 부재로 노출하지 않는다. - [x] **Task 6.2: I18n 문구와 asset 참조 확인** - 대상 파일: - 수정: `SodaLive/Sources/I18n/I18n.swift` - 확인: `SodaLive/Resources/Assets.xcassets/v2/ic_arrow_up_gray.imageset/Contents.json` - 확인: `SodaLive/Resources/Assets.xcassets/v2/ic_arrow_up_white.imageset/Contents.json` - 작업 내용: - `I18n.CreatorChannelCommunity.commentPlaceholder` 또는 같은 범위의 문구로 `댓글을 입력하세요` ko/en/ja를 추가한다. - 비밀댓글 toggle 문구는 기존 `I18n.Explorer.secretComment`를 재사용한다. - `ic_arrow_up_gray`, `ic_arrow_up_white` asset이 존재하는지 확인하고 다른 arrow asset 이름을 참조하지 않는다. - 검증 기준: - 실행 명령: `rg "commentPlaceholder|댓글을 입력하세요|secretComment|ic_arrow_up_gray|ic_arrow_up_white" SodaLive/Sources/I18n/I18n.swift SodaLive/Sources/V2/CreatorChannel/Community/Detail SodaLive/Resources/Assets.xcassets/v2` - 기대 결과: 문구와 icon 참조가 확정된 asset 이름과 일치한다. - [x] **Task 6.3: 프로젝트 파일 등록 확인** - 대상 파일: - 확인/수정: `SodaLive.xcodeproj/project.pbxproj` - 작업 내용: - 신규 Swift 파일이 Xcode target에 포함되는지 확인한다. - 수동 등록이 필요한 경우 기존 V2 파일 등록 패턴을 따라 최소 변경으로 추가한다. - 검증 기준: - 실행 명령: `rg "CreatorChannelCommunityPostDetailView|CreatorChannelCommunityPostDetailViewModel|CreatorChannelCommunityPostDetailResponse|CreatorChannelCommunityPostDetailInputBar" SodaLive.xcodeproj/project.pbxproj` - 기대 결과: 신규 Swift 파일의 project 등록이 확인된다. ### Phase 7: 최종 검증 - [x] **Task 7.1: 정적 문서/파일 검증** - 대상 파일: - 확인: `docs/20260707_커뮤니티_게시글_상세/prd.md` - 확인: `docs/20260707_커뮤니티_게시글_상세/plan-task.md` - 확인: 구현 변경 파일 전체 - 검증 기준: - 실행 명령: `git diff --check -- docs/20260707_커뮤니티_게시글_상세 SodaLive/Sources/V2/CreatorChannel/Community SodaLive/Sources/V2/CreatorChannel/ReplyDetail SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift SodaLive/Sources/I18n/I18n.swift SodaLive.xcodeproj/project.pbxproj` - 기대 결과: 공백 오류 없이 성공한다. - 실행 명령: `plutil -lint SodaLive.xcodeproj/project.pbxproj` - 기대 결과: `OK`가 출력된다. - [x] **Task 7.2: 빌드 검증** - 대상 파일: - 확인: 전체 앱 target - 검증 기준: - 실행 명령: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` - 기대 결과: Debug 빌드 성공. - 참고: 테스트 번들 타깃이 확인되지 않는 저장소 상태이므로, 단위 테스트가 추가되지 않은 경우 빌드 검증을 우선 성공 기준으로 둔다. - [ ] **Task 7.3: 수동 QA 시나리오 기록** - 대상 파일: - 수정: `docs/20260707_커뮤니티_게시글_상세/plan-task.md` - 검증 기준: - 댓글 가능 게시글 상세 진입 시 상세 API가 호출되고 게시글/댓글/입력 바가 표시된다. - 댓글 불가능 게시글 상세 진입 시 좋아요는 표시되고 댓글 icon/count, 댓글 목록, 입력 바는 숨겨진다. - 댓글 입력값이 없으면 `ic_arrow_up_gray`/`gray900`, 입력값이 있으면 `ic_arrow_up_white`/`soda400`가 표시된다. - 4줄 이상 입력 시 입력창 높이가 3줄로 유지되고 내부 스크롤된다. - 비밀댓글 toggle 노출 조건에서 toggle 값이 댓글 작성 API의 `isSecret`으로 전달된다. - 댓글 row tap 시 기존 답글 상세 화면으로 진입하고 신규 답글 조회 API가 호출된다. - 게시글을 쓴 크리에이터 계정에서는 내가 작성하지 않은 댓글/답글에도 `삭제하기`가 표시된다. - 작성자도 게시글 크리에이터도 아닌 계정에서는 신고 action이 표시되지 않는다. [blocked] 기존 `ReportRequest`/`ReportType`에 community comment report identifier가 없어 백엔드/API 계약 확정 전까지 수동 QA 대상에서 제외한다. - 하트 tap 시 기존 좋아요 API가 호출되고 icon/count가 갱신된다. - 실행 후 이 문서의 `검증 기록` 섹션에 실행 일자, 명령, 결과를 누적한다. ## 검증 기록 - 2026-07-07: `docs/20260707_커뮤니티_게시글_상세/prd.md`, `docs/agent-guides/build-test-verification.md`, 기존 `docs/20260707_글_상세_답글_쓰기/plan-task.md`, `docs/20260705_크리에이터_채널_커뮤니티_탭/plan-task.md`를 확인해 구현 범위와 문서 형식을 정했다. - 2026-07-07: 현재 코드 기준으로 `AppStep.creatorChannelReplyDetail`, `CreatorChannelReplyDetailView`, V2 커뮤니티 탭 파일, V2 커뮤니티 API/Repository, 답글 상세 display 모델이 이미 존재함을 확인하고, 게시글 상세는 신규 `Community/Detail` 하위 구현으로 계획했다. - 2026-07-07: 기존 `CreatorCommunityCommentItemView`에서 게시글 크리에이터 또는 댓글 작성자이면 더보기 버튼을 표시하고, 수정은 작성자에게만, 삭제는 작성자 또는 게시글 크리에이터에게 허용하는 것을 확인했다. 계획 문서에 동일 권한 규칙을 반영했다. - 2026-07-07: 사용자 확인 사항에 따라 신고 규칙도 기존 커뮤니티 댓글과 동일하게 검토 대상으로 두었으나, 구현 확인 결과 기존 `ReportRequest`/`ReportType`에 community comment report identifier가 없고 기존 `CreatorCommunityCommentItemView`도 신고 action을 노출하지 않아 계획 문서에 [blocked]로 정정했다. - 2026-07-07: `plutil -lint SodaLive.xcodeproj/project.pbxproj` 실행 결과 `OK`를 확인했다. - 2026-07-07: `git diff --check -- docs/20260707_커뮤니티_게시글_상세 SodaLive/Sources/V2/CreatorChannel/Community SodaLive/Sources/V2/CreatorChannel/ReplyDetail SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift SodaLive/Sources/I18n/I18n.swift SodaLive.xcodeproj/project.pbxproj` 실행 결과 공백 오류 없이 통과했다. - 2026-07-07: `xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build` 실행 결과 `BUILD SUCCEEDED`를 확인했다. - 2026-07-07: `rg "canReport|onTapReport|showReportError|CreatorChannelActionPopupWithReport|private struct CreatorChannelActionPopup|신고" SodaLive/Sources/V2/CreatorChannel/Community/Detail` 실행 결과 fake report 제거 후 출력이 없음을 확인했다. - 2026-07-07: 커뮤니티 댓글 신고 흐름은 기존 `ReportRequest`/`ReportType`에 커뮤니티 댓글 신고 식별자가 없어 연결하지 못했다. 구현은 기존 `CreatorCommunityCommentItemView`의 수정/삭제 popup 동작과 동일하게 맞췄고, fake report action은 노출하지 않는다. - 2026-07-07: 정적 검증과 Debug 빌드 검증은 완료했다. Task 7.3의 device/simulator/API-backed 환경 수동 runtime QA는 아직 수행하지 않았다.