38 KiB
커뮤니티 게시글 상세 구현 계획
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.swiftSodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swiftSodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swiftSodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swiftSodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentListView.swiftSodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentRow.swiftSodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailInputBar.swift
수정
SodaLive/Sources/V2/CreatorChannel/Community/Repository/CreatorChannelCommunityApi.swiftSodaLive/Sources/V2/CreatorChannel/Community/Repository/CreatorChannelCommunityRepository.swiftSodaLive/Sources/V2/CreatorChannel/Community/CreatorChannelCommunityTabView.swiftSodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swiftSodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityGridItem.swiftSodaLive/Sources/V2/CreatorChannel/ReplyDetail/Models/CreatorChannelReplyDetailContext.swiftSodaLive/Sources/V2/CreatorChannel/ReplyDetail/Models/CreatorChannelReplyDetailDisplayItem.swiftSodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailViewModel.swiftSodaLive/Sources/App/AppStep.swiftSodaLive/Sources/ContentView.swiftSodaLive/Sources/I18n/I18n.swiftSodaLive.xcodeproj/project.pbxproj
확인/재사용
SodaLive/Sources/V2/CreatorChannel/Community/Components/CreatorChannelCommunityListItem.swiftSodaLive/Sources/V2/Component/Card/CommunityPostCard.swiftSodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailView.swiftSodaLive/Sources/V2/CreatorChannel/FanTalk/Components/CreatorChannelFanTalkListItem.swiftSodaLive/Sources/Explorer/Profile/CreatorCommunity/CreatorCommunityRepository.swiftSodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/ModifyCommunityPostCommentRequest.swift
TASK 체크리스트
Phase 1: 조회 모델과 API
-
Task 1.1: 커뮤니티 게시글 상세 응답 모델 생성
- 대상 파일:
- 생성:
SodaLive/Sources/V2/CreatorChannel/Community/Models/CreatorChannelCommunityPostDetailResponse.swift
- 생성:
- 작업 내용:
CreatorChannelCommunityPostDetailResponse를Decodable,Identifiable로 정의한다.CreatorChannelCommunityCommentsResponse,CreatorChannelCommunityCommentResponse,CreatorChannelCommunityReplyResponse,CreatorChannelCommunityRepliesResponse를 정의한다.- Kotlin
Long필드는 SwiftInt로 선언한다. 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가 확인된다.
- 실행 명령:
- 대상 파일:
-
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: 라우팅과 목록 진입
-
Task 2.1: 게시글 상세 route 추가
- 대상 파일:
- 수정:
SodaLive/Sources/App/AppStep.swift - 수정:
SodaLive/Sources/ContentView.swift
- 수정:
- 작업 내용:
AppStep에creatorChannelCommunityPostDetail(postId:onCommunityRefresh:)case를 추가한다.ContentViewswitch에서CreatorChannelCommunityPostDetailView(postId:onCommunityRefresh:)를 반환한다.onCommunityRefresh는 상세 mutation 성공 후 커뮤니티 탭 첫 페이지를 갱신하기 위한 optional callback으로 둔다.
- 검증 기준:
- 실행 명령:
rg "creatorChannelCommunityPostDetail|CreatorChannelCommunityPostDetailView|onCommunityRefresh" SodaLive/Sources/App/AppStep.swift SodaLive/Sources/ContentView.swift - 기대 결과: 새 route와 화면 연결이 확인된다.
- 실행 명령:
- 대상 파일:
-
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에
onTapDetailclosure를 추가한다. - item 전체 tap은 상세 route로 연결한다.
- 좋아요, 댓글, 더보기, 구매, 오디오 재생 버튼 tap은 상세 진입과 충돌하지 않게 button action으로 유지한다.
- 상세 route에는
post.postId와viewModel.fetchFirstPage(creatorId:)callback을 전달한다.
- 리스트 item과 그리드 item에
- 검증 기준:
- 실행 명령:
rg "onTapDetail|creatorChannelCommunityPostDetail|fetchFirstPage\\(creatorId: creatorId\\)|contentShape|Button" SodaLive/Sources/V2/CreatorChannel/Community - 기대 결과: 리스트/그리드 item tap 상세 진입과 내부 버튼 action 분리가 확인된다.
- 실행 명령:
- 대상 파일:
Phase 3: 상세 ViewModel과 mutation 재사용
-
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이면 댓글 입력/목록 상태를 비활성 상태로 유지한다.isShowSecretcomputed 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 - 기대 결과: 상세 조회와 댓글 입력 상태가 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 3.2: 댓글 페이지네이션 구현
- 대상 파일:
- 수정:
SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift
- 수정:
- 작업 내용:
fetchNextCommentsIfNeeded(currentComment:)를 추가한다.- 마지막 댓글 노출 시
hasNext == true이면page + 1로 댓글 API를 호출한다. - 중복 호출을 막기 위해
isLoadingNextPage와isLoadingguard를 둔다. - 첫 페이지 재조회 시 기존 댓글 목록을 교체하고, 다음 페이지 성공 시 append한다.
- 검증 기준:
- 실행 명령:
rg "fetchNextCommentsIfNeeded|getCommunityPostComments|isLoadingNextPage|hasNext|page \\+ 1|append\\(contentsOf:" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift - 기대 결과: 댓글 추가 조회와 append 로직이 확인된다.
- 실행 명령:
- 대상 파일:
-
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 반영이 확인된다.
- 실행 명령:
- 대상 파일:
-
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 성공 시
onCommunityRefreshcallback을 호출해 목록 갱신을 예약한다.
- 신규 댓글 작성은
- 검증 기준:
- 실행 명령:
rg "createCommunityPostComment|parentId: nil|isSecret|ModifyCommunityPostCommentRequest|modifyComment|isActive = false|onCommunityRefresh|commentText = \"\"" SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailViewModel.swift - 기대 결과: 기존 커뮤니티 댓글 mutation API 재사용이 확인된다.
- 실행 명령:
- 대상 파일:
Phase 4: 상세 UI 컴포넌트
-
Task 4.1: 상세 게시글 콘텐츠 컴포넌트 구현
- 대상 파일:
- 생성:
SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift
- 생성:
- 작업 내용:
- Figma 기준으로 작성자 프로필, 닉네임, 상대 시간, 본문, 이미지/잠금/오디오 영역, 좋아요/댓글 reaction을 표시한다.
- 반응 정보는 댓글 icon/count를 먼저, 좋아요 icon/count를 뒤에 표시한다.
- 유료 미구매 상태는
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가 확인된다.
- 실행 명령:
- 대상 파일:
-
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 분리가 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 4.3: 댓글 목록 컴포넌트 구현
- 대상 파일:
- 생성:
SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentListView.swift
- 생성:
- 작업 내용:
- section header는
댓글 {commentCount}를 표시한다. - 댓글 목록은
LazyVStack으로 표시하고 마지막 댓글onAppear에서 다음 페이지 조회를 요청한다. - 댓글 row 사이에는 Figma 기준
gray8001pt separator를 표시한다. - 댓글이 비어 있으면 header만 유지하거나 Figma 기준 빈 목록 영역을 유지한다.
- 선택된 댓글의 popup은 기존
CreatorChannelActionPopup또는CreatorChannelReplyDetailActionPopup기반으로 수정/삭제 action을 표시한다.
- section header는
- 검증 기준:
- 실행 명령:
rg "CreatorChannelCommunityPostDetailCommentListView|댓글|commentCount|LazyVStack|onAppear|fetchNext|CreatorChannelActionPopup|onTapEdit|onTapDelete" SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailCommentListView.swift - 기대 결과: 댓글 목록, 페이지네이션 trigger, popup action 연결이 확인된다.
- 실행 명령:
- 대상 파일:
-
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를 최소 범위로 제공한다.
- placeholder는
- 검증 기준:
- 실행 명령:
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줄 확장, 전송 버튼 상태가 확인된다.
- 실행 명령:
- 대상 파일:
-
Task 4.5: 상세 화면 조립
- 대상 파일:
- 생성:
SodaLive/Sources/V2/CreatorChannel/Community/Detail/CreatorChannelCommunityPostDetailView.swift
- 생성:
- 작업 내용:
CreatorChannelReplyDetailTitleBar를 재사용해 back-only title bar를 표시한다.- 댓글 가능 상세에서는 게시글 영역과 댓글 목록 사이에 Figma 기준
gray8001pt separator를 표시한다. 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: 답글 상세 재사용 확장
-
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을 만들 수 있다.
- 실행 명령:
- 대상 파일:
-
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를 사용해 답글 페이지네이션을 처리한다.
- 신규 상세 댓글 context에서는
- 검증 기준:
- 실행 명령:
rg "getCommunityCommentReplies|CreatorChannelCommunityRepliesResponse|CreatorChannelCommunityReplyResponse|hasNext|communityReplyPage|fetchNextCommunityRepliesIfNeeded" SodaLive/Sources/V2/CreatorChannel/ReplyDetail/CreatorChannelReplyDetailViewModel.swift - 기대 결과: 신규 답글 조회 API와 기존 V1 답글 조회 경로가 분리되어 확인된다.
- 실행 명령:
- 대상 파일:
-
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 성공 시 호출할onCommunityRefreshcallback을 추가하고, 게시글 상세 진입 시viewModel.fetchDetail(postId:)를 전달한다.
- 댓글 row tap 시
- 검증 기준:
- 실행 명령:
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: 권한, 문구, 프로젝트 등록
-
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 계약 부재로 노출하지 않는다.
- 실행 명령:
- 대상 파일:
-
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_whiteasset이 존재하는지 확인하고 다른 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 이름과 일치한다.
- 실행 명령:
- 대상 파일:
-
Task 6.3: 프로젝트 파일 등록 확인
- 대상 파일:
- 확인/수정:
SodaLive.xcodeproj/project.pbxproj
- 확인/수정:
- 작업 내용:
- 신규 Swift 파일이 Xcode target에 포함되는지 확인한다.
- 수동 등록이 필요한 경우 기존 V2 파일 등록 패턴을 따라 최소 변경으로 추가한다.
- 검증 기준:
- 실행 명령:
rg "CreatorChannelCommunityPostDetailView|CreatorChannelCommunityPostDetailViewModel|CreatorChannelCommunityPostDetailResponse|CreatorChannelCommunityPostDetailInputBar" SodaLive.xcodeproj/project.pbxproj - 기대 결과: 신규 Swift 파일의 project 등록이 확인된다.
- 실행 명령:
- 대상 파일:
Phase 7: 최종 검증
-
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가 출력된다.
- 실행 명령:
- 대상 파일:
-
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가 갱신된다.
- 실행 후 이 문서의
검증 기록섹션에 실행 일자, 명령, 결과를 누적한다.
- 대상 파일:
Phase 8: 구매완료 배지 제거
Phase 결과: 구매한 유료 커뮤니티 게시글 상세에서 구매완료 배지가 표시되지 않고 기존 잠금·구매·상세 콘텐츠 흐름은 유지된다.
선행조건: PRD DEC-001 확정.
Task 8.1 구매완료 배지 렌더링 제거
Goal 실행 P8-T1: 상세 콘텐츠의 구매완료 배지만 최소 범위로 제거한다.
- 완료 증거:
purchase_badge_absentcontract check 통과, 인접 잠금/reaction contract 유지,SodaLive-devDebug 빌드 성공. - 범위 밖:
existOrdered모델, 잠금 overlay, 구매 다이얼로그/API, 목록·그리드 UI 변경. - TDD 방식: 테스트 번들 타깃이 없는 현재 저장소 제약에 따라 source contract check를 focused test로 사용한다.
Files:
-
Modify:
SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift -
Modify:
docs/20260707_커뮤니티_게시글_상세/prd.md -
Modify:
docs/20260707_커뮤니티_게시글_상세/plan-task.md -
RED:
purchase_badge_absentcontract check를 정의한다. -
RED 확인:
if rg -n 'purchaseCompleteTag|Text\("구매완료"\)' SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift; then exit 1; fi를 실행해 3개 매치와 exit code 1을 확인한다. -
GREEN: header의 구매완료 조건부 렌더링과
purchaseCompleteTagcomputed property를 제거한다. -
GREEN 확인: 같은
purchase_badge_absentcontract check가 매치 없이 exit code 0으로 성공하는지 확인한다. -
REFACTOR: 추가 정리 없이 diff가 배지 렌더링 제거로만 제한되는지 확인한다.
Task 8.2 회귀 및 빌드 검증
Goal 실행 P8-GATE: 구매완료 배지 제거와 인접 유료 게시글 흐름 보존을 판정한다.
rg -n 'isPaidLocked|Button\(action: onTapPurchase\)|paidLockedOverlay|reactionBar' SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift에서 잠금·구매·reaction 경로가 유지되는지 확인한다.git diff --check -- docs/20260707_커뮤니티_게시글_상세 SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift가 성공하는지 확인한다.xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build가BUILD SUCCEEDED로 끝나는지 확인한다.- 가능한 simulator/API-backed 환경에서 구매한 유료 글의 배지 미표시, 미구매 글의 잠금·가격 표시, 무료 글의 상세 콘텐츠를 확인하고 결과 또는 불가 사유를 검증 기록에 남긴다.
검증 기록
- 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는 아직 수행하지 않았다.
- 2026-08-10: PRD
DEC-001에 구매완료 배지 제거와 잠금·구매 흐름 유지 범위를 확정했다. 구현 전purchase_badge_absentcontract check는purchaseCompleteTag와Text("구매완료")3개 매치로 exit code 1이 되어 의도한 RED 실패를 확인했다. - 2026-08-10: header의 구매완료 조건부 렌더링과
purchaseCompleteTag만 제거했다. 같은purchase_badge_absentcontract check는 0개 매치로 통과했고,isPaidLocked,Button(action: onTapPurchase),paidLockedOverlay,reactionBar8개 매치가 유지됨을 확인했다. - 2026-08-10:
git diff --check -- docs/20260707_커뮤니티_게시글_상세 SodaLive/Sources/V2/CreatorChannel/Community/Detail/Components/CreatorChannelCommunityPostDetailContentView.swift는 출력 없이 성공했다.xcodebuild -workspace "SodaLive.xcworkspace" -scheme "SodaLive-dev" -configuration Debug build는BUILD SUCCEEDED로 통과했다. - 2026-08-10: iPhone 16 Pro Simulator에
SodaLive-dev를 설치·실행해 앱 홈 화면 진입 screenshot을 확보했다. 상세 화면 직접 수동 QA는 앱 공개 deeplink가 상세가 아닌 커뮤니티 목록으로 이동하고, macOS GUI 자동화는 Apple Events 권한-1743으로 차단되며, 사용자가 LLDB 기반 주입 검증을 건너뛰라고 지시해 수행하지 않았다. 따라서 실제 API-backed 구매/미구매/무료 상세 화면의 pixel QA는 미검증으로 남기고, source contract·Debug build·앱 launch smoke로 대체했다. - 2026-08-10: 리뷰 리포트
docs/20260707_커뮤니티_게시글_상세/reviews/phase8-remove-purchase-complete-badge.md를 작성했다. 초기 리뷰에서 회귀 검색 매치 수 기록 불일치가 확인되어 8개로 정정했고, 같은 Oracle 리뷰 세션의 재검증에서 PASS, blocker 없음 판정을 받았다.