# 커뮤니티 게시물 상세 / 댓글 / 답글 구현 계획/TASK > **For agentic workers:** 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. 구현 범위 변경이 생기면 이 문서를 먼저 수정한 뒤 코드에 반영한다. **Goal:** 크리에이터 채널 `커뮤니티` 탭에서 게시글 item 터치 시 게시물 상세로 진입해 본문/좋아요/댓글을 조회·작성하고, 댓글의 답글 목록 화면(팬Talk 상세와 동일 UI)에서 답글을 조회·작성하며, 수정/삭제/좋아요는 기존 API를 재사용한다. **Architecture:** 조회(상세/댓글/답글)는 신규 v2 endpoint를 v2 API/Repository로 호출한다. 쓰기/수정/삭제/좋아요는 기존 `CreatorCommunityRepository`를 그대로 호출한다. 화면은 커뮤니티 전용으로 신규 구현하되, 답글 목록 화면의 layout XML과 댓글/답글 item view만 재사용 가능하게 설계한다. **Tech Stack:** Kotlin, Android XML Views, ViewBinding, RxJava3, Koin, JUnit4/Robolectric. --- ## 전제와 성공 기준 - PRD: `docs/20260708_커뮤니티_게시물_상세_댓글_답글/prd.md` - 기존 커뮤니티 탭 문서: - `docs/20260621_크리에이터_채널_커뮤니티_탭/prd.md` - `docs/20260621_크리에이터_채널_커뮤니티_탭/plan-task.md` - 재사용 vs 복사 결정: 커뮤니티 전용 신규 구현. layout XML + 댓글/답글 item view만 데이터/콜백 주입형으로 공용화(데이터 계층은 커뮤니티 전용). 근거는 PRD `11. Open Questions & Decisions` 참고. - 조회 API는 신규 v2 endpoint를 사용하고 기본값 `page=0`, `size=20`을 따른다. - 쓰기/수정/삭제/좋아요/팝업 메뉴는 기존 커뮤니티 코드를 재사용한다. - 구현 완료 판단 시 Gradle 검증뿐 아니라 Figma 4개 화면의 주요 layout, 하트/전송 버튼 상태, 댓글 불가 처리, 입력 영역 동작이 실제 구현 화면에 반영되었는지 확인한다. - Figma 대조 결과는 문서 최하단 `Verification Log`에 한국어로 누적 기록한다. - 구현 완료 후 최소 다음 명령을 실행한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` - `./gradlew :app:mergeDebugResources` - `./gradlew :app:compileDebugKotlin` - `./gradlew :app:ktlintCheck` - `git diff --check` --- ## Figma 참조 - 게시글 상세(댓글 가능 + 댓글 있음): `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` - 게시글 상세(댓글 불가능): `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` - 댓글 답글 목록(답글 없음): `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=669-40462&m=dev` - 댓글 답글 목록(답글 있음): `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=669-40468&m=dev` --- ## API 계약 - 조회(신규 v2, `ApiResponse`): - 상세: `GET /api/v2/creator-channels/community-posts/{postId}` → `CreatorChannelCommunityPostDetailResponse` - 필드: `postId`, `creatorId`, `creatorNickname`, `creatorProfileUrl`, `createdAtUtc`, `content`, `imageUrl`, `audioUrl`, `price`, `isCommentAvailable`, `existOrdered`, `likeCount`, `commentCount`, `isPinned`, `isLiked`, `comments` - `comments`는 댓글 첫 페이지(`CreatorChannelCommunityCommentsResponse`)를 포함한다. - 댓글: `GET /api/v2/creator-channels/community-posts/{postId}/comments?page=0&size=20` → `CreatorChannelCommunityCommentsResponse` - 필드: `commentCount`, `comments`, `page`, `size`, `hasNext` - 댓글 필드: `commentId`, `writerId`, `writerProfileImageUrl`, `writerNickname`, `content`, `isSecret`, `createdAtUtc`, `latestReply` - 최신 답글 필드: `commentId`, `writerId`, `writerProfileImageUrl`, `writerNickname`, `content`, `createdAtUtc` - 답글: `GET /api/v2/creator-channels/community-comments/{commentId}/replies?page=0&size=20` → `CreatorChannelCommunityRepliesResponse` - 쓰기/수정/삭제/좋아요(기존 `CreatorCommunityRepository` 재사용): - 좋아요: `communityPostLike(postId, token)` - 댓글 쓰기: `registerComment(postId, comment, token = token)` - 답글 쓰기: `registerComment(postId, comment, parentId = commentId, token = token)` - 댓글/답글 수정: `modifyComment(ModifyCommentRequest(commentId, comment = ...), token)` - 댓글/답글 삭제: `modifyComment(ModifyCommentRequest(commentId, isActive = false), token)` - 팝업 메뉴: `R.menu.content_comment_option_menu`(내 댓글/답글: 수정/삭제), `R.menu.content_comment_option_menu2`(채널 크리에이터가 보는 타인 댓글/답글: 삭제). 일반 타인 댓글/답글은 커뮤니티 댓글 전용 신고 API가 없어 더보기 버튼을 노출하지 않는다. --- ## 파일 구조 (예정) - 생성(응답 모델): `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/data/CreatorChannelCommunityDetailResponse.kt` - PRD의 `CreatorChannelCommunityPostDetailResponse`, `CreatorChannelCommunityCommentsResponse`, `CreatorChannelCommunityCommentResponse`, `CreatorChannelCommunityReplyResponse`, `CreatorChannelCommunityRepliesResponse`. - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelApi.kt` - 상세/댓글/답글 조회 endpoint 3개 추가. - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt` - 상세/댓글/답글 조회 method 추가. - 생성(진입점): 커뮤니티 탭 item 터치 → 상세 화면 이동. - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt`(item click을 Host callback으로 위임). - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt`, `CreatorChannelCommunityGridAdapter.kt`(item click 콜백 추가). - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt`(상세 화면 실행). - 생성(상세 화면): `.../community/detail/CreatorChannelCommunityDetailActivity.kt`, `CreatorChannelCommunityDetailViewModel.kt` - 생성(답글 화면): `.../community/detail/reply/CreatorChannelCommunityReplyActivity.kt`, `CreatorChannelCommunityReplyViewModel.kt` - 생성(layout): `activity_creator_channel_community_detail.xml`, `activity_creator_channel_community_reply.xml`, 댓글/답글 item layout, 입력 영역 layout, 전송 버튼 배경 drawable(enabled/disabled). - 생성(어댑터/item view): 댓글 목록 adapter, 답글 목록 adapter(재사용 가능한 댓글/답글 item view). - 수정: `app/src/main/AndroidManifest.xml`(신규 Activity 등록), `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`(신규 ViewModel binding). - 수정: `values`, `values-en`, `values-ja` strings(placeholder 등 신규 문자열 또는 기존 재사용). - 테스트 생성: 상세/답글 ViewModel 테스트, layout/source 테스트. --- ### Phase 1: 조회 API/모델 계약 - [x] **Task 1.1: 응답 데이터 클래스 생성** - 생성: `.../community/detail/data/CreatorChannelCommunityDetailResponse.kt` - 작업: PRD 명시 5개 data class를 `@Keep`/`@SerializedName` 포함해 정의한다. - 검증: `./gradlew :app:compileDebugKotlin` - 검증 기록(2026-07-08): TDD RED로 `CreatorChannelCommunityDetailContractTest` 추가 후 DTO 패키지/클래스 미존재로 `:app:compileDebugUnitTestKotlin` 실패를 확인했다. 이후 `CreatorChannelCommunityPostDetailResponse`, `CreatorChannelCommunityCommentsResponse`, `CreatorChannelCommunityCommentResponse`, `CreatorChannelCommunityReplyResponse`, `CreatorChannelCommunityRepliesResponse`를 생성하고 `./gradlew :app:compileDebugKotlin` 성공을 확인했다. - [x] **Task 1.2: v2 API/Repository 조회 method 추가** - 수정: `CreatorChannelApi.kt`, `CreatorChannelRepository.kt` - 작업: 상세/댓글/답글 조회 3개 추가(기본 `page=0`, `size=20`, token 포함). - 검증: `./gradlew :app:compileDebugKotlin` - 검증 기록(2026-07-08): 구현 전 `CreatorChannelApi`/`CreatorChannelRepository`의 상세/댓글/답글 method 미존재로 RED 실패를 확인했다. 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` 및 `./gradlew :app:compileDebugKotlin` 성공을 확인했다. --- ### Phase 2: 상세 ViewModel/화면 - [x] **Task 2.1: 상세 ViewModel RED 테스트 작성** - 생성: `CreatorChannelCommunityDetailViewModelTest.kt` - 케이스: 상세 로드, 좋아요 토글(낙관적 업데이트+실패 롤백, 중복 방지), 댓글 pagination(hasNext), 댓글 쓰기 성공 후 새로고침, 댓글 불가 시 댓글 조회/입력 미노출. - 검증: 해당 테스트 실행(구현 전 실패). - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailViewModelTest`를 먼저 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityDetailViewModelTest"`를 실행해 `CreatorChannelCommunityDetailViewModel`/`CreatorChannelCommunityDetailUiState` 미존재 컴파일 오류로 RED 실패를 확인했다. - [x] **Task 2.2: 상세 ViewModel 구현** - 생성: `CreatorChannelCommunityDetailViewModel.kt` - 검증: `CreatorChannelCommunityDetailViewModelTest` PASS. - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailViewModel`과 상세/댓글/답글 UI state model을 추가하고 동일한 테스트 명령을 재실행해 PASS를 확인했다. 상세 조회, 댓글 첫 페이지/페이지네이션, 좋아요 낙관적 토글/중복 방지/실패 롤백, 댓글 trim 작성/성공 이벤트/댓글 새로고침, 댓글 불가 시 댓글 API/작성 미호출을 검증했다. - [x] **Task 2.3: 상세 layout/Activity 구현** - 생성: `activity_creator_channel_community_detail.xml`, `CreatorChannelCommunityDetailActivity.kt`, 댓글 adapter/item, 입력 영역, 전송 버튼 drawable. - 작업: Figma `290:9243`(댓글 가능)/`290:9266`(댓글 불가) 기준. 하트 fill/empty, 전송 버튼 gray_900/soda_400 상태, 3줄 자동확장/4줄 스크롤, 댓글 불가 시 댓글/입력 영역 숨김, 최신 답글 1개 중첩 표시, 더보기 팝업 메뉴 재사용. - 수정: `AndroidManifest.xml`, `AppDI.kt`. - 검증: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin` - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailReplyUiContractTest`를 먼저 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`를 실행해 상세/답글 layout·id·source 미존재 컴파일 오류로 RED 실패를 확인했다. 이후 `activity_creator_channel_community_detail.xml`, `CreatorChannelCommunityDetailActivity`, 댓글 adapter/item, 입력 bar 배경/전송 icon drawable, 문자열, manifest/AppDI 등록을 추가했다. 상세 화면은 `newIntent(context, postId)`로 진입하고 `postId <= 0`은 toast 후 종료하며, 하트 토글/댓글 입력 trim 활성화/댓글 불가 시 댓글 목록과 하단 입력 숨김/최신 답글 card 클릭 답글 화면 진입/팝업 메뉴 재사용 계약을 반영했다. --- ### Phase 3: 답글 화면 - [x] **Task 3.1: 답글 ViewModel RED 테스트/구현** - 생성: `CreatorChannelCommunityReplyViewModel(.kt/Test.kt)` - 케이스: 부모 댓글/답글 로드, 답글 pagination, 답글 쓰기(`parentId = commentId`) 후 새로고침, 수정/삭제 후 새로고침. - 검증: 테스트 PASS. - 검증 기록(2026-07-08): `CreatorChannelCommunityReplyViewModelTest`를 먼저 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityReplyViewModelTest"`를 실행해 `CreatorChannelCommunityReplyViewModel`/`CreatorChannelCommunityReplyUiState` 미존재 컴파일 오류로 RED 실패를 확인했다. 이후 `CreatorChannelCommunityReplyViewModel`을 추가하고 동일 명령을 재실행해 PASS를 확인했다. 부모 댓글 보존/답글 첫 페이지 조회, `hasNext` 페이지네이션과 중복 `loadMore` 방지, 답글 trim 작성/blank 무시/중복 전송 방지/성공 후 입력·이벤트 정리 및 새로고침, 수정·삭제 후 새로고침, `commentId <= 0`일 때 답글 API 미호출을 검증했다. - [x] **Task 3.2: 답글 layout/Activity 구현** - 생성: `activity_creator_channel_community_reply.xml`, `CreatorChannelCommunityReplyActivity.kt`, 재사용 가능한 댓글/답글 item view/adapter. - 작업: Figma `669:40462`(답글 없음)/`669:40468`(답글 있음) 기준. 부모 댓글 상단 표시, 답글 들여쓰기, placeholder `답글을 입력하세요`, 더보기 팝업 메뉴 재사용. - 수정: `AndroidManifest.xml`, `AppDI.kt`. - 검증: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin` - 검증 기록(2026-07-08): 상세 UI 계약 RED 테스트에 답글 layout/source 계약을 함께 추가해 실패를 확인한 뒤 `activity_creator_channel_community_reply.xml`, `CreatorChannelCommunityReplyActivity`, 답글 adapter/item을 생성했다. 답글 화면은 primitive extras로 부모 댓글을 재구성하고 상단 부모 댓글 표시, 들여쓰기 답글 card, `답글을 입력하세요` 입력 bar, trim 기반 전송 버튼 상태, `updateReplyInput`/`submitReply`, 팝업 메뉴의 `modifyReply`/`deleteReply` 연결을 반영했다. --- ### Phase 4: 진입점 연결 - [x] **Task 4.1: 커뮤니티 탭 item 터치 → 상세 진입** - 수정: `CreatorChannelCommunityListAdapter.kt`, `CreatorChannelCommunityGridAdapter.kt`, `CreatorChannelCommunityFragment.kt`(Host callback 추가), `CreatorChannelActivity.kt`. - 작업: item 터치 시 `postId`로 상세 화면 실행. 댓글에서 답글 화면(`commentId`) 진입 연결. 좋아요/댓글/답글 작성 후 목록 새로고침 정책 연결. - 검증: `./gradlew :app:compileDebugKotlin` - 검증 기록(2026-07-08): `CreatorChannelCommunityFragmentLayoutTest`와 `CreatorChannelActivitySourceTest`에 RED source 계약을 먼저 추가해 list/grid adapter root click callback, fragment Host 위임, Activity의 상세 화면 `newIntent` 진입 계약 실패를 확인했다. 이후 list/grid adapter에 `onPostClick`을 추가하고 play/owner more click은 기존 콜백으로 분리 유지했으며, fragment는 `postId`를 Host로 위임하고 Activity는 `postId <= 0L`을 무시한 뒤 `CreatorChannelCommunityDetailActivity.newIntent(this, postId)`로 상세 화면을 실행하도록 연결했다. --- ### Phase 5: 테스트/회귀 검증 - [x] **Task 5.1: source/layout 테스트 추가** - 작업: 조회 API page/size 기본값, 하트 icon 계약(`ic_feed_community_heart_fill`/`ic_feed_community_heart`), 전송 버튼 상태(`ic_new_arrow_up_gray`+gray_900 / `ic_new_arrow_up_white`+soda_400), 댓글 불가 시 댓글/입력 숨김, placeholder 문구를 검증. - 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` - 검증 기록(2026-07-08): review-work 차단 이슈 회귀를 막기 위해 상세 댓글 수정/삭제 ViewModel 테스트와 상세/답글 source 계약 테스트를 먼저 추가했다. 구현 전 동일 targeted 테스트를 실행해 `CreatorChannelCommunityDetailViewModel.modifyComment/deleteComment` 미구현 컴파일 오류로 RED 실패를 확인했다. 구현 후 동일 명령을 재실행해 PASS를 확인했고, 상세/답글 Activity의 하단 도달 pagination 호출, 내 댓글/답글에만 destructive popup 노출, 수정 입력 dialog, 삭제 확인 dialog, 상세 오디오 매니저 연결/lifecycle 정리, 상세 로드 Error finish 계약을 source 테스트로 검증했다. - [x] **Task 5.2: 전체 검증 실행** - 실행: 위 성공 기준의 Gradle 명령 전체. - Figma 수동 확인: `290:9243`, `290:9266`, `669:40462`, `669:40468` 대조. - 검증 기록: `Verification Log`에 누적. - 검증 기록(2026-07-08): review-work 차단 이슈 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`이 PASS했다. 최초 `./gradlew :app:ktlintCheck`는 신규 테스트 source 문자열 길이 초과로 실패했으며, 테스트 문자열 분리 후 재실행해 PASS했다. `git diff --check`는 출력 없이 PASS했다. --- ### Phase 6: review 재반영 및 서버 계약 수정 - [x] **Task 6.1: 서버 댓글 응답 계약 반영** - 작업: 상세 응답 내부 `comments`와 댓글/최신 답글의 `writer*`/`content` 필드명을 DTO와 UI mapping에 반영한다. - 검증: `CreatorChannelCommunityDetailContractTest`, 상세/답글 ViewModel 테스트 PASS. - 검증 기록(2026-07-08): `CreatorChannelCommunityPostDetailResponse`에 `comments`를 추가하고, `CreatorChannelCommunityCommentsResponse.commentCount`, 댓글/답글의 `writerId`, `writerProfileImageUrl`, `writerNickname`, `content` 계약을 반영했다. 상세 최초 댓글 목록은 상세 응답의 embedded `comments`에서 사용하고, 답글 수정/삭제 ID는 응답의 `commentId`를 답글 자체 ID로 매핑했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS로 확인했다. - [x] **Task 6.2: 댓글/답글 refresh 중 stale pagination 응답 방지** - 작업: 상세 댓글과 답글 pagination 요청에 generation guard를 두고, 새로고침 시 이전 요청이 현재 state를 덮지 않게 한다. - 검증: stale 응답 회귀 테스트 PASS. - 검증 기록(2026-07-08): 상세 댓글과 답글 ViewModel에 request generation guard를 추가해 새 상세/답글 로드 후 이전 page 응답이 도착해도 현재 목록을 덮지 않도록 수정했다. stale 응답 회귀 테스트를 추가하고 community targeted test PASS를 확인했다. - [x] **Task 6.3: 수정 dialog blank 입력 안내 보강** - 작업: 댓글/답글/부모 댓글 수정에서 공백 입력 시 기존 커뮤니티 정책과 동일한 내용 입력 toast를 표시한다. - 검증: source 또는 Activity 계약 테스트 PASS. - 검증 기록(2026-07-08): 댓글/답글/부모 댓글 수정 요청이 공백이면 `screen_creator_community_write_content_hint` toast 이벤트를 노출하고 legacy modify API를 호출하지 않도록 보강했다. ViewModel 회귀 테스트 PASS를 확인했다. - [x] **Task 6.4: 레이아웃 속성 직접 검증 테스트 축소** - 작업: UI layout 크기/padding/source 문자열 검증을 제거하고, 로직/상태/리소스 존재 계약 중심으로 테스트를 줄인다. - 검증: targeted unit test PASS. - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailReplyUiContractTest`에서 layout 높이/padding/drawable 문자열 및 Activity source 문자열 직접 검증을 제거하고, inflate 가능한 layout과 필수 view id/문자열/AppDI/manifest 등록 확인으로 축소했다. community targeted test PASS를 확인했다. ### Phase 7: review 결과 반영 - [x] **Task 7.1: embedded 댓글의 채널 크리에이터 권한 매핑 수정** - 작업: 상세 응답의 embedded `comments`를 최초 UI state로 매핑할 때 `Content` state가 아직 없어 `isCreatorOwner`가 false로 고정되는 문제를 수정한다. - 검증: 채널 크리에이터가 타인 댓글/최신 답글에 대해 삭제 메뉴 권한을 갖는 ViewModel 회귀 테스트 PASS. - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailViewModelTest`에 채널 작성자가 상세 embedded 타인 댓글의 삭제 메뉴 권한을 갖는 회귀 테스트를 추가하고, 수정 전 `AssertionError` RED 실패를 확인했다. 이후 embedded 댓글은 상세 응답의 `creatorId`, 추가 페이지 댓글은 현재 post의 `creatorId` 기준으로 `isCreatorOwner`를 명시 전달하도록 수정해 동일 테스트 PASS를 확인했다. ### Phase 8: 리뷰 결과 추가 반영 - [x] **Task 8.1: 댓글 불가 화면/댓글·답글 item UI 정리** - 작업: 댓글 불가 화면에서는 댓글 목록/입력 영역만 숨기고 별도 안내 문구를 제거한다. 댓글/답글 item에서 Figma/PRD에 없는 reaction row를 제거한다. - 검증: layout/source 계약 테스트 및 community targeted test PASS. - 검증 기록(2026-07-08): `CreatorChannelCommunityDetailReplyUiContractTest`에 댓글 불가 안내 view 제거, 댓글/답글 reaction id 제거 계약을 추가했다. `item_creator_channel_community_comment.xml`과 `item_creator_channel_community_reply.xml`에서 Figma에 없는 reaction row를 제거하고 adapter binding 참조를 정리했다. 상세 댓글 불가 화면은 댓글 header/list/input bar만 숨기도록 유지했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS로 확인했다. - [x] **Task 8.2: 유료 미구매 paywall 표시 수정** - 작업: 유료 미구매 게시물은 raw media URL을 노출하지 않되 Figma 중앙 lock + 가격 paywall 영역은 표시한다. - 검증: 상세 ViewModel/Activity/layout 계약 테스트 PASS. - 검증 기록(2026-07-08): `CreatorChannelCommunityPostDetailUiModel.showPaywall`을 추가하고 유료 미구매 타인 게시물에서 `imageUrl`/`audioUrl`은 계속 제거하되 `showPaywall`을 true로 노출하도록 수정했다. 상세 layout에는 `layout_creator_channel_community_detail_paywall`과 `tv_creator_channel_community_detail_paywall_price`를 추가해 이미지 컨테이너 중앙에 lock+가격 pill을 표시한다. 상세 ViewModel/UI 계약 테스트 PASS로 확인했다. - [x] **Task 8.3: 댓글/답글 상대시간 보정** - 작업: v2 `createdAtUtc` 파싱을 ISO offset/초 단위 epoch까지 지원하고, 서버·단말 시계 차이로 작성 직후 과거 시간이 표시되지 않도록 최근 시간 보정 규칙을 추가한다. 답글 화면 부모 댓글도 원본 `createdAtUtc`를 전달해 답글 목록과 같은 포맷터 기준으로 표시한다. - 검증: 상대시간 포맷터 회귀 테스트 및 상세/답글 ViewModel 테스트 PASS. - 검증 기록(2026-07-08): 기존 공통 함수 `formatUtcRelativeTimeText`를 보강해 epoch millis 기존 동작을 유지하면서 epoch seconds, ISO offset, 6자리 fraction을 파싱하도록 수정했다. ISO 문자열은 서버/단말 시계 오차를 고려해 10분 이내를 `방금전`으로 보정한다. 댓글/답글 UI model에 원본 `createdAtUtc`를 보존하고 답글 화면 `Intent`에 전달해 부모 댓글도 동일한 `UtcRelativeTimeTextFormatter`로 재포맷하도록 수정했다. `RelativeTimeFormatterTest`, 상세/답글 ViewModel 테스트 PASS로 확인했다. --- ## Verification Log - 구현 전 문서 작성 단계에서는 Gradle 검증을 실행하지 않았다. - 2026-07-08: 추가 review findings 반영 범위를 확정했다. 상세/답글 변경 결과를 상위 커뮤니티 목록과 상세 댓글/답글 카운트에 반영하고, 좋아요 API의 `success=false` 응답에서 낙관적 상태를 롤백하며, 댓글/답글 입력창을 3줄까지 자동 확장되도록 고정 높이를 제거하고, 수정 내용이 없을 때 기존 `audio_content_comment_no_change` toast를 표시하도록 보강한다. - 2026-07-08: Phase 2.3/3.2 UI 구현 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`를 실행해 신규 activity/layout/item/source가 없어 `compileDebugUnitTestKotlin` RED 실패를 확인했다. - 2026-07-08: Phase 2.3/3.2 UI 구현 후 동일 targeted 테스트를 재실행해 48개 테스트 PASS를 확인했다. Robolectric inflate로 상세/답글 layout의 title back/content/input bar 및 item layout 계약을 확인했고, source 검사로 AppDI/manifest/ViewModel binding/popup menu/comment hidden/newIntent 계약을 확인했다. - 2026-07-08: review-work 차단 이슈 대응으로 Phase 5 테스트를 보강했다. RED는 `CreatorChannelCommunityDetailViewModel.modifyComment/deleteComment` 미구현 컴파일 오류로 확인했고, GREEN은 community targeted 테스트와 CreatorChannelActivity source 테스트, resources merge, Kotlin compile, ktlint, diff whitespace 검증 PASS로 확인했다. - 2026-07-08: 최종 확인으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 재실행해 모두 `BUILD SUCCESSFUL`을 확인했다. `git diff --check`는 출력 없이 통과했다. - 2026-07-08: review-work 재검증에서 발견된 추가 차단 이슈를 반영했다. 상세 댓글 추가 로딩은 실제 스크롤 컨테이너인 `NestedScrollView` 기준으로 감지하도록 수정하고, 유료 미구매 잠금 게시글은 작성자가 아닌 경우 이미지/오디오 URL을 UI model에서 제거해 raw media URL 노출을 막았다. 댓글/답글 더보기는 레거시와 동일하게 작성자 또는 채널 크리에이터에게만 노출하며, 답글 화면 부모 댓글에도 수정/삭제 메뉴를 연결했다. PRD의 상세 응답 `comments` 표현은 별도 댓글 endpoint 첫 페이지 조회 계약으로 정리했다. - 2026-07-08: 추가 차단 이슈 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 재실행해 모두 `BUILD SUCCESSFUL`을 확인했다. `git diff --check`는 출력 없이 통과했다. - 2026-07-08: 부모 댓글 수정/삭제 후 답글 화면의 부모 댓글 영역과 상세 댓글 목록이 stale 상태로 남을 수 있다는 review-work 지적을 반영했다. 부모 댓글 수정 성공 시 답글 ViewModel의 `parentComment` 상태를 즉시 갱신하고, 부모 댓글 변경/삭제 이벤트를 Activity result로 상세 화면에 전달해 상세를 재조회하도록 수정했다. 삭제 성공 시 답글 화면은 `RESULT_OK` 반환 후 종료한다. 회귀 테스트로 부모 댓글 수정 상태 갱신, 삭제 이벤트, ActivityResult source 계약을 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` PASS를 확인했다. - 2026-07-08: 추가 review findings 4건을 반영했다. 상세 진입은 `ActivityResultLauncher`로 변경해 상세의 댓글/좋아요/수정/삭제 및 답글 화면 변경 결과가 상위 커뮤니티 목록 refresh로 전파되도록 했고, 좋아요 API의 `success=false` 응답은 낙관적 상태를 이전 좋아요 상태/개수로 롤백하도록 수정했다. 댓글/답글 작성·수정·삭제 후에는 상세/답글을 서버 재조회해 댓글·답글 count를 동기화하고, 댓글/답글 입력창은 고정 높이를 제거해 3줄까지 자동 확장되도록 `wrap_content`/`minHeight=40dp`/`maxLines=3`/vertical scrollbar 계약을 맞췄다. 수정 내용이 원문과 같으면 `audio_content_comment_no_change` toast 후 API 호출을 막도록 상세/답글 수정 dialog를 보강했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했고, `git diff --check`는 출력 없이 통과했다. 최초 병렬 Gradle 실행 중 KSP 증분 캐시 손상/동시 접근 오류가 발생했으나 `app/build/kspCaches/debug` 생성 캐시 제거 후 순차 재실행으로 통과했다. - 2026-07-08: review-work 재검증에서 답글 화면 변경 결과가 상세 화면 재조회까지만 반영되고 상위 `CreatorChannelActivity` 목록 refresh로 재전파되지 않는 차단 이슈가 확인되어 수정했다. `CreatorChannelCommunityDetailActivity`의 `replyLauncher`에서 답글 화면 `RESULT_OK` 수신 시 `setResult(Activity.RESULT_OK)`를 먼저 호출한 뒤 `loadDetail(postId)`를 수행하도록 변경했고, source contract test가 이 순서를 검증하도록 보강했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했고, `git diff --check`는 출력 없이 통과했다. - 2026-07-08: 추가 review findings 3건을 반영했다. 커뮤니티 게시물 상세 진입 전 `SharedPreferenceManager.token.isBlank()`이면 기존 `showLoginActivity()`로 이동하고 상세 Activity launch를 중단하도록 보강했다. 댓글/답글 작성·수정·삭제 실패 시 v2 `CreatorChannelEvent` 기반 toast 이벤트를 노출해 API 메시지 또는 `common_error_unknown`을 표시하도록 상세/답글 ViewModel과 Activity를 수정했다. 상세 댓글 목록 상단에는 `댓글 {commentCount}` 헤더를 추가하고, 답글 화면 부모 댓글에서는 reply/heart count reaction row와 latest reply block을 숨겨 Figma/PRD 정적 UI와 맞췄다. 회귀 테스트로 로그인 gate source 계약, 실패 toast 이벤트 consume 계약, 댓글 헤더/부모 reaction row source·layout 계약을 추가했으며 targeted unit test는 `BUILD SUCCESSFUL`을 확인했다. - 2026-07-08: 리뷰 결과 3건과 서버 응답 계약 불일치를 반영했다. 상세 응답 내부 `comments`를 최초 댓글 목록으로 사용하도록 바꾸고, 댓글/최신 답글/답글 응답 DTO를 서버 계약의 `writer*`, `content`, `commentId` 필드명에 맞췄다. 상세 댓글/답글 pagination에는 generation guard를 추가해 새로고침 후 stale page 응답이 현재 목록을 덮지 않게 했고, 댓글/답글/부모 댓글 수정 blank 입력은 `screen_creator_community_write_content_hint` toast 후 API를 호출하지 않도록 했다. UI 계약 테스트는 layout 속성/source 문자열 직접 검증을 제거해 필수 view/resource 등록 중심으로 축소했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했다. `git diff --check`, `git diff HEAD --check`는 출력 없이 통과했다. `:app:mergeDebugResources`는 최초 sandbox에서 `~/.gradle` lock 파일 권한 문제로 실패했으나, 동일 명령을 권한 상승 재실행해 통과했다. - 2026-07-08: 코드 리뷰에서 발견된 채널 크리에이터 권한 매핑 문제를 반영했다. 상세 응답 embedded `comments`를 최초 UI state로 매핑할 때 아직 `Content` state가 없어 `isCreatorOwner`가 false로 고정되던 문제를, 상세 응답의 `creatorId` 또는 현재 post의 `creatorId`를 기준으로 명시 전달하도록 수정했다. 회귀 테스트는 수정 전 RED 실패 후 수정 후 PASS를 확인했다. 최종 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했다. `git diff --check`, `git diff HEAD --check`는 출력 없이 통과했다. `:app:mergeDebugResources`는 최초 sandbox에서 `~/.gradle` lock 파일 권한 문제로 실패했으나, 동일 명령을 권한 상승 재실행해 통과했다. - 2026-07-08: 리뷰 결과 추가 반영을 완료했다. 댓글 불가 화면은 댓글 목록/header/input bar만 숨기고 별도 안내 문구 view를 제거했다. 댓글/답글 item에서 Figma에 없는 reaction row를 제거했다. 유료 미구매 상세는 raw image/audio URL을 UI model에서 계속 제거하면서 `showPaywall`로 중앙 lock+가격 paywall을 표시하도록 수정했다. 상대시간은 기존 공통 `formatUtcRelativeTimeText`를 보강해 epoch seconds, ISO offset, 6자리 fraction, 작성 직후 서버/단말 시계 차이를 처리하고, 답글 화면 부모 댓글에는 원본 `createdAtUtc`를 전달해 동일 포맷터 기준으로 표시되게 했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.RelativeTimeFormatterTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`임을 확인했다. `git diff --check`, `git diff HEAD --check`는 출력 없이 통과했다. `:app:mergeDebugResources`는 최초 sandbox에서 `~/.gradle` lock 파일 권한 문제로 실패했으나, 동일 명령을 권한 상승 재실행해 통과했다.