# 커뮤니티 게시물 상세 / 댓글 / 답글 구현 계획/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로 확인했다. ### Phase 9: 댓글/답글 수정 UX 후속 반영 - [x] **Task 9.1: 타이틀/메뉴/모달 계약 정리** - 수정: `activity_creator_channel_community_detail.xml`, `activity_creator_channel_community_reply.xml`, `content_comment_option_menu.xml`, `content_comment_option_menu2.xml`, 상세/답글 Activity. - 작업: 상세/답글 titlebar의 `커뮤니티`, `답글` 문구를 제거하고, 댓글/답글 더보기 메뉴 문구를 `수정하기`, `삭제하기`로 바꾼다. v2 패키지 내부 삭제 확인은 `V2ModalDialog`를 사용한다. - 검증: layout/source 계약 테스트, `./gradlew :app:compileDebugKotlin` - 검증 기록(2026-07-09): 상세/답글 layout에서 title `TextView`를 제거하고, 댓글 메뉴 리소스는 `creator_channel_fantalk_edit`/`creator_channel_fantalk_delete`를 사용하도록 수정했다. 상세/답글 Activity의 삭제 확인은 `V2ModalDialog`로 교체하고 `AlertDialog` 사용을 제거했다. `CreatorChannelCommunityDetailReplyUiContractTest`에 title id 제거, 메뉴 문구, `V2ModalDialog` source 계약을 추가했으며 targeted community test와 `./gradlew :app:compileDebugKotlin` PASS로 확인했다. - [x] **Task 9.2: 하단 입력창 기반 수정 모드 구현** - 수정: `CreatorChannelCommunityDetailViewModel.kt`, `CreatorChannelCommunityReplyViewModel.kt`, 상세/답글 Activity/layout, 관련 테스트. - 작업: 수정 선택 시 별도 입력 Dialog 없이 하단 입력창에 기존 내용을 채우고 X 취소 버튼을 표시한다. 수정 모드의 send는 쓰기가 아니라 수정 API를 호출하며, 성공/취소 시 일반 입력 모드로 복귀한다. - 검증: 상세/답글 ViewModel 회귀 테스트, layout/source 계약 테스트, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"` - 검증 기록(2026-07-09): 상세 댓글은 `editingCommentId`/`editingOriginalComment`, 답글 화면은 `CreatorChannelCommunityReplyEditTarget`으로 수정 모드를 상태화했다. 수정 선택 시 하단 입력창에 기존 내용을 세팅하고 X 버튼을 표시하며, 수정 모드의 send는 댓글/답글/부모 댓글 수정 API를 호출한다. 성공 또는 X 취소 시 입력과 수정 target을 초기화한다. 변경 없음과 공백 입력은 기존 toast 정책을 유지했다. ViewModel 회귀 테스트와 layout/source 계약 테스트를 추가했고 targeted community test PASS로 확인했다. - [x] **Task 9.3: 후속 검증** - 실행: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`. - 검증 기록: `Verification Log`에 누적한다. - 검증 기록(2026-07-09): `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` 실행 결과 모두 통과했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation 경고는 기존 설정 경고로 남아 있다. ### Phase 10: 댓글/답글 더보기 메뉴 폰트 후속 반영 - [x] **Task 10.1: 더보기 메뉴 regular font 적용** - 수정/생성: `CreatorChannelCommunityMorePopup.kt`, `view_creator_channel_community_more_popup.xml`, 댓글/답글 adapter, UI 계약 테스트. - 작업: 기본 `PopupMenu` 대신 `PopupWindow` 기반 커스텀 메뉴를 사용하고, 메뉴 TextView에 `android:fontFamily="@font/regular"`를 명시한다. - 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`. - 검증 기록(2026-07-09): 댓글/답글 더보기 메뉴를 `PopupMenu`에서 `CreatorChannelCommunityMorePopup`으로 교체했다. 새 `view_creator_channel_community_more_popup.xml`의 `수정하기`, `삭제하기` TextView에 `@font/regular`를 명시했고, 내 댓글/답글은 수정+삭제, 채널 작성자가 보는 타인 댓글/답글은 삭제만 노출하는 기존 권한 규칙을 유지했다. UI 계약 테스트에 popup layout font와 adapter source 계약을 추가했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`가 모두 통과했다. ### Phase 11: 댓글/답글 입력창 키보드 후속 반영 - [x] **Task 11.1: 수정 진입/성공 시 키보드 제어** - 수정: `CreatorChannelCommunityDetailActivity.kt`, `CreatorChannelCommunityReplyActivity.kt`, UI 계약 테스트. - 작업: `수정하기` 터치 후 하단 입력창 수정 모드로 전환될 때 입력창에 focus를 주고 키보드를 즉시 올린다. 댓글/답글 작성 또는 수정 API 성공 이벤트 수신 시 키보드를 내리고 focus를 해제한다. - 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`. - 검증 기록(2026-07-09): 상세/답글 Activity에 `showSoftInput`/`hideSoftInputFromWindow` 기반 키보드 제어를 추가했다. 수정 target이 새로 설정되면 입력창 focus 후 키보드를 올리고, 댓글 작성/수정 및 답글 작성/수정/부모 댓글 수정 성공 이벤트에서 키보드를 내리도록 연결했다. UI 계약 테스트에 IME 제어 source 계약을 추가했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`가 모두 통과했다. ### Phase 12: 커뮤니티 상세 UI border 후속 반영 - [x] **Task 12.1: 상세 상단 게시글 배경과 댓글 경계선 수정** - 수정: `app/src/main/res/layout/activity_creator_channel_community_detail.xml`, `app/src/main/res/layout/item_creator_channel_community_comment.xml`, `CreatorChannelCommunityCommentAdapter.kt` - 작업: 상세 게시글 컨테이너의 `bg_feed_card` rounded 배경을 제거하고, 게시글과 댓글 헤더 사이 1dp border를 추가한다. 댓글 item 사이에는 1dp border를 표시하되 마지막 댓글 하단 border는 숨긴다. - 검증: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityDetailReplyUiContractTest"`, `./gradlew :app:ktlintCheck`, `git diff --check` - 검증 기록(2026-07-12): 상세 게시글 컨테이너의 `bg_feed_card` 배경을 제거해 목록형 rounded card 배경이 상세 상단 본문에는 적용되지 않도록 했다. 게시글과 댓글 헤더 사이에는 `gray_800` 1dp divider를 추가하고 댓글 가능 상태에서만 노출되도록 Activity binding을 연결했다. 댓글 item 하단 divider에는 id를 부여하고 adapter에서 마지막 댓글의 divider를 숨겨 댓글 사이 border로만 보이게 했다. `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityDetailReplyUiContractTest"`, `./gradlew :app:ktlintCheck`, `git diff --check`가 모두 통과했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation 경고는 기존 설정 경고로 남아 있다. ### Phase 13: 커뮤니티 상세 오디오/구매 후속 반영 - [x] **Task 13.1: 상세 오디오 중앙 버튼과 구매 회귀 테스트 추가** - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailViewModelTest.kt`, `CreatorChannelCommunityDetailReplyUiContractTest.kt` - 작업: 상세 layout에 이미지 중앙 play/pause 버튼이 있고, Activity가 `PurchaseCommunityPostDialog`와 `purchaseCommunityPost`를 연결하며, ViewModel이 구매 성공 후 상세를 재조회하는 계약을 RED로 고정한다. - 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityDetailViewModelTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityDetailReplyUiContractTest"` - 검증 기록(2026-07-12): 상세 layout의 중앙 오디오 버튼 id, legacy 구매 dialog 연결, ViewModel 구매 성공 후 상세 재조회 계약을 테스트에 추가했다. 최초 병렬 Gradle 실행은 120초/300초 timeout 및 병렬 resource packaging 충돌성 `NoSuchFileException`으로 완료 결과를 얻지 못했으나, 구현 후 순차 재실행에서 동일 테스트들이 PASS했다. - [x] **Task 13.2: 상세 오디오 중앙 버튼과 구매 흐름 구현** - 수정: `app/src/main/res/layout/activity_creator_channel_community_detail.xml`, `CreatorChannelCommunityDetailActivity.kt`, `CreatorChannelCommunityDetailViewModel.kt` - 작업: `item_creator_community_all.xml`의 `iv_play_or_pause`처럼 상세 이미지 중앙 버튼을 추가하고, 재생 상태에 따라 play/pause drawable을 갱신한다. 상세 paywall 가격 클릭 시 기존 구매 dialog를 띄우고 구매 성공 후 상세를 다시 로드한다. - 검증: targeted test PASS, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin` - 검증 기록(2026-07-12): `activity_creator_channel_community_detail.xml`의 이미지 컨테이너 중앙에 `iv_creator_channel_community_detail_play_or_pause`를 추가하고 `btn_audio_content_play`/`btn_audio_content_pause`를 사용하도록 연결했다. `CreatorChannelCommunityDetailActivity`는 이미지+오디오 게시물에서 중앙 버튼을 표시하고, 이미지 없는 오디오 게시물은 기존 텍스트 오디오 버튼을 유지한다. paywall 영역 클릭 시 기존 `PurchaseCommunityPostDialog`를 띄우고 확인 시 `CreatorChannelCommunityDetailViewModel.purchaseCommunityPost()`를 호출한다. ViewModel은 기존 `CreatorCommunityRepository.purchaseCommunityPost`를 호출한 뒤 성공 시 상세를 다시 로드한다. `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin` PASS로 확인했다. - [x] **Task 13.3: 후속 검증** - 실행: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` - 검증 기록: `Verification Log`에 누적한다. - 검증 기록(2026-07-12): `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityDetailViewModelTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityDetailReplyUiContractTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`가 모두 통과했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation 경고와 Gradle deprecated feature 경고는 기존 설정 경고로 남아 있다. --- ### Phase 14: 유료 미구매 상세 반응/댓글 숨김 후속 - [x] **Task 14.1: 상세 유료 미구매 게시글 reaction/comment 숨김** - 대상 파일: - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailViewModel.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt` - `app/src/main/res/layout/activity_creator_channel_community_detail.xml` - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailViewModelTest.kt` - 작업: `price > 0 && !existOrdered && creatorId != SharedPreferenceManager.userId` 잠금 게시글에서는 상세의 reaction 현황, 댓글 목록, 댓글 헤더, 댓글 입력을 숨긴다. - 검증 기록: - 2026-07-12: RED로 `CreatorChannelCommunityDetailViewModelTest.유료 미구매 게시물은 작성자가 아니면 reaction 현황과 댓글을 숨긴다`를 추가했고, production 변경 전 `showReaction` 미구현 컴파일 실패를 확인했다. 이후 상세 UI 모델/상태와 Activity binding에 잠금 조건을 반영해 focused 테스트 GREEN을 확인했다. --- ### Phase 15: 커뮤니티 구매 진입점·표시 상태·상세 메뉴 정리 **Phase 결과:** 홈과 커뮤니티 탭은 게시글 상태를 동일하게 표시하고 상세 이동만 수행하며, 구매와 권한별 관리/신고는 상세에서 수행한다. **선행조건:** PRD `F0-20260810` 승인, 홈 Response의 `isPinned`/`isCommentAvailable` 서버 계약 확정. **Phase 완료 조건:** `P15-T1`~`P15-T5`와 `P15-GATE` 완료, Task별 검증 기록 및 문서 최하단 `Verification Log` 누적. **실행 제약:** 2026-08-10 사용자 지시에 따라 이번 계획 작성 단계에서는 구현과 Git 커밋을 수행하지 않는다. 이후 구현도 별도 사용자 요청 전에는 시작하지 않으며, 이 Phase에는 commit 단계를 두지 않는다. #### Phase 15 파일 구조 - 홈 계약/표시: - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItem.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt` - `app/src/main/res/layout/view_feed_community.xml` - 커뮤니티 탭: - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityUiModels.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMappers.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt` - `app/src/main/res/layout/item_creator_channel_community_list.xml` - 확인만 수행: `app/src/main/res/layout/item_creator_channel_community_grid.xml` - 상세/결과 전파: - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailViewModel.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` - `app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChange.kt` - `app/src/main/res/layout/activity_creator_channel_community_detail.xml` - 기존 테스트 수정: - `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItemTest.kt` - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeModelsTest.kt` - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityMapperTest.kt` - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragmentLayoutTest.kt` - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailViewModelTest.kt` - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailReplyUiContractTest.kt` - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` - `app/src/test/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChangeTest.kt` #### Task 15.1 홈 Response와 게시글 표시 정책 **Goal 실행 `P15-T1`:** 홈 커뮤니티 Response의 고정/댓글 가능 계약과 잠금·reaction 표시 조건을 순수 로직으로 고정한다. - **시작 조건:** PRD `Home Response Contract`, `Home Community Requirements` 승인. - **완료 증거:** 신규 DTO 필드 파싱 및 `FeedItem.Community` 표시 정책 focused test PASS. - **범위 밖:** View binding, 커뮤니티 탭, 상세 메뉴. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelHomeModels.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItem.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeModelsTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItemTest.kt` **Interfaces:** - Consumes: 홈 JSON `isPinned: Boolean`, `isCommentAvailable: Boolean`, 기존 `price`, `existOrdered`. - Produces: `FeedItem.Community`에 추가할 member: ```kotlin val isPinned: Boolean = false val isCommentAvailable: Boolean = true val hideReactionWhenLocked: Boolean = false val isLocked: Boolean val showReaction: Boolean val showComment: Boolean ``` - `isLocked = price > 0 && !existOrdered` - `showReaction = !hideReactionWhenLocked || !isLocked` - `showComment = showReaction && isCommentAvailable` - [x] **RED:** `CreatorChannelHomeModelsTest`의 홈 JSON에 `isPinned=true`, `isCommentAvailable=false` 커뮤니티를 넣고 두 필드 보존을 검증한다. - [x] **RED:** `FeedItemTest`에 잠금 숨김 opt-in 유료 미구매는 reaction/comment 모두 false, 기본값 유료 미구매는 기존 reaction/comment 표시 유지, 댓글 불가 무료 글은 reaction true/comment false, 고정 상태 보존 케이스를 추가한다. - [x] **RED 확인:** 아래 명령을 실행해 신규 DTO/property 미존재로 인한 컴파일 또는 assertion 실패를 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeModelsTest" \ --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedItemTest" ``` - [x] **GREEN:** `CreatorChannelCommunityPostResponse`에 `@SerializedName("isPinned")`, `@SerializedName("isCommentAvailable")` 필드를 추가하고 `FeedItem.Community`에 다른 화면의 기존 표시를 유지하는 기본값 및 위 계산 property를 최소 구현한다. - [x] **GREEN 확인:** 같은 focused test가 PASS하는지 확인한다. - [x] **REFACTOR:** 신규 별도 presentation class를 만들지 않고 `FeedItem.Community` 계산 property만 유지한다. - 검증 기록(2026-08-10): DTO/property 추가 전 focused test의 미존재 컴파일 RED를 확인했다. 구현 및 `P15-R3` 범위 보정 후 홈 JSON 신규 필드, 잠금 opt-in/default, 댓글 불가, 고정 상태 테스트가 최종 Gate에서 PASS했다. #### Task 15.2 홈 커뮤니티 표시 연결 **Goal 실행 `P15-T2`:** 홈 커뮤니티 item이 잠금·댓글 가능·고정 상태를 표시하고 card 전체 상세 이동 외의 액션을 제공하지 않는다. - **시작 조건:** `P15-T1` 완료. - **완료 증거:** `P15-T1` 정책을 실제 bind에 사용하고 resource merge/Kotlin compile PASS. - **범위 밖:** 커뮤니티 탭 item과 상세 메뉴. - **TDD 예외 사유:** XML view 위치·visibility 자체를 직접 검증하는 테스트는 저장소 규칙상 작성하지 않는다. 표시 분기는 `P15-T1` 순수 로직 test로 검증한다. - **대체 검증 방법:** View binding id 생성, resource merge, Kotlin compile, 기존 `FeedViewTest` 회귀를 실행한다. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt` - Modify: `app/src/main/res/layout/view_feed_community.xml` - Verify: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedViewTest.kt`(수정 없이 기존 회귀만 실행) **Interfaces:** - Consumes: `FeedItem.Community.isPinned`, `showReaction`, `showComment`. - Produces: `FeedCommunityView.bind(item)`의 공지/reaction 상태와 기존 `FeedCommunityView` root click 계약. - [x] `CreatorChannelCommunityPostResponse.toFeedCommunityItem()`이 `isPinned`, `isCommentAvailable`을 전달하도록 수정한다. - [x] `view_feed_community.xml`에 커뮤니티 탭과 같은 공지 row와 reaction row/comment icon 식별 id를 추가한다. - [x] `FeedCommunityView.bind()`가 공지 row, reaction row, 댓글 icon/count, 하트 icon/count를 `FeedItem.Community` 계산 property로 bind하도록 수정한다. - [x] `CreatorChannelHomeSectionAdapter.bindCommunities()`의 `row.setOnClickListener { onCommunityClick(community.postId) }`만 item 액션으로 유지한다. - [x] 아래 명령으로 focused 회귀 및 compile을 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedItemTest" \ --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedViewTest" ./gradlew :app:mergeDebugResources :app:compileDebugKotlin ``` - 검증 기록(2026-08-10): 홈 mapper/bind/root click을 정적 대조했고 `P15-R2`에서 reaction id 위치를 정정했다. `FeedItemTest`, `FeedViewTest`, resource merge, Kotlin compile이 PASS했다. #### Task 15.3 커뮤니티 탭 상세 이동 전용화와 가격 UI 통일 **Goal 실행 `P15-T3`:** 커뮤니티 탭 리스트/그리드 item 액션을 상세 이동 하나로 제한하고 잠금 가격 UI를 홈/상세 구조로 통일한다. - **시작 조건:** 기존 list/grid `onPostClick` 상세 routing 계약 확인. - **완료 증거:** play/owner-more callback 제거, list/grid root 상세 이동 유지, mapper/routing focused test 및 compile PASS. - **범위 밖:** 상세 화면 내부 구매·메뉴 동작. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityUiModels.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMappers.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt` - Modify: `app/src/main/res/layout/item_creator_channel_community_list.xml` - Verify only: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt` - Verify only: `app/src/main/res/layout/item_creator_channel_community_grid.xml` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityMapperTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragmentLayoutTest.kt` **Interfaces:** - Consumes: `CreatorChannelCommunityPostUiModel.postId`, `price`, `isLocked`, `showOwnerTopPrice`. - Produces: list/grid adapter constructor의 단일 item callback `onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit`. - [x] **RED:** routing/source 계약 테스트에 Fragment/list adapter가 `onPlayClick`, `onOwnerMoreClick`, `toggleCommunityAudio`, `onCreatorChannelCommunityOwnerMoreClicked`를 갖지 않고 list/grid root가 `onPostClick`만 호출한다는 케이스를 추가한다. - [x] **RED 확인:** 아래 명령으로 현재 하위 액션이 남아 있어 실패함을 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityMapperTest" \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest" ``` - [x] **GREEN:** Fragment의 media player lifecycle/재생 함수와 Host owner-more callback, list adapter의 play/owner-more callback 및 더보기 view를 제거한다. - [x] **GREEN:** UI model/mapper에서 이번 변경으로 미사용이 된 `audioUrl`, `showPlayButton`, `showOwnerMore`만 제거하고 작성자 상단 가격인 `showOwnerTopPrice`는 유지한다. - [x] **GREEN:** 기존 Fragment/layout/source test에서 제거된 play/owner-more의 positive assertion만 삭제하거나 상세 이동 단일 callback의 negative assertion으로 교체한다. View 크기·간격·constraint·visibility 속성 assertion은 새로 추가하지 않는다. - [x] **GREEN:** 리스트 잠금 금액의 compound drawable `TextView`를 그리드/상세와 같은 별도 `ImageView(ic_bar_cash)` + 금액 `TextView` capsule 구조로 변경한다. - [x] **GREEN:** 그리드는 이미 `lock icon + cash icon + 금액 capsule` 구조와 root 상세 이동만 사용하므로 신규 production 변경 없이 계약 일치만 확인한다. - [x] **GREEN 확인:** focused test, `./gradlew :app:mergeDebugResources :app:compileDebugKotlin`을 실행한다. - [x] **REFACTOR:** play/owner-more 제거로 생긴 import·binding·Host 구현 orphan만 삭제하고 다른 탭 코드는 정리하지 않는다. - 검증 기록(2026-08-10): 하위 액션 제거 계약의 4개 RED 실패를 확인한 뒤 list/grid root `onPostClick`만 유지했다. mapper/layout focused test, resource merge, Kotlin compile과 최종 Gate가 PASS했다. #### Task 15.4 상세 더보기 권한과 게시글 mutation **Goal 실행 `P15-T4`:** 상세 작성자 영역 우측 끝에서 권한별 관리/신고 메뉴를 제공하고 성공/실패 상태를 정확히 전파한다. - **시작 조건:** 상세 DTO의 `creatorId`, `price`, `existOrdered`, `isPinned` 계약과 기존 레거시 dialog/API 확인. - **완료 증거:** 더보기 노출 4개 권한 케이스, 고정/삭제/신고 API와 event focused test PASS, resource merge/compile PASS. - **범위 밖:** 홈/커뮤니티 탭 새로고침 실행. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailViewModel.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/detail/CreatorChannelCommunityDetailActivity.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt` - Modify: `app/src/main/res/layout/activity_creator_channel_community_detail.xml` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailViewModelTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityDetailReplyUiContractTest.kt` **Interfaces:** - Produces UI model fields: `CreatorChannelCommunityPostDetailUiModel`에 추가할 member: ```kotlin val isOwner: Boolean val showMore: Boolean ``` - `isOwner = creatorId == SharedPreferenceManager.userId` - `showMore = isOwner || price == 0 || existOrdered` - Produces ViewModel methods/events: ```kotlin fun updateCommunityPostFixed() fun deleteCommunityPost() fun reportCommunityPost(reason: String) val postDeletedEventLiveData: LiveData ``` - Produces repository method: ```kotlin fun reportCommunityPost(postId: Long, reason: String, token: String) ``` - [x] **RED:** 상세 ViewModel test에 작성자 유료 미구매=true, 일반 무료=true, 일반 유료 구매=true, 일반 유료 미구매=false인 `showMore` 케이스를 추가한다. - [x] **RED:** 고정 성공은 `isPinned` 반전 요청 후 상세 재조회와 `postChangedEventLiveData=true`, 삭제 성공은 `postDeletedEventLiveData=true`, 신고는 `ReportType.COMMUNITY_POST` repository 호출만 수행하는 케이스를 추가한다. - [x] **RED:** UI 계약 test에 `R.id.btn_creator_channel_community_detail_more` resource와 Activity의 modify launcher/기존 BottomSheet·신고 dialog 연결 계약을 추가한다. 위치·constraint·visibility 값은 test에서 직접 검증하지 않는다. - [x] **RED 확인:** 아래 명령으로 신규 fields/methods 미존재 실패를 확인한다. ```bash ./gradlew :app:testDebugUnitTest --tests \ "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityDetailViewModelTest" ``` - [x] **GREEN:** `CreatorChannelRepository.reportCommunityPost()`가 `ReportRequest(ReportType.COMMUNITY_POST, reason, communityPostId = postId)`를 기존 `ReportRepository`에 위임하도록 추가한다. - [x] **GREEN:** DetailViewModel의 고정은 `updateCommunityPostFixed(postId, !content.post.isPinned, authToken())`, 삭제는 `ModifyCommunityPostRequest(creatorCommunityId = postId, isActive = false)`를 JSON `text/plain` RequestBody로 변환한 `modifyCommunityPost(postImage = null, ...)`, 신고는 신규 report method를 호출한다. 각 mutation의 중복 요청을 막고 실패 시 기존 Toast event만 내보낸다. - [x] **GREEN:** 신고 성공은 API message 또는 기존 `character_comment_report_submitted` 문구를 Toast event로 전달하고 게시글 변경 event는 내보내지 않는다. - [x] **GREEN:** 상세 layout에서 `layout_creator_channel_community_detail_price`와 `tv_creator_channel_community_detail_purchased`를 제거하고, 작성자 영역 우측 끝에 `@drawable/ic_seemore_vertical`, `@string/read_more` content description을 사용하는 `btn_creator_channel_community_detail_more`를 추가한다. 닉네임/작성 시간의 end constraint는 이 버튼 start에 연결한다. - [x] **GREEN:** Activity는 `post.showMore`로 더보기 버튼 visibility를 bind한다. - [x] **GREEN:** Activity에서 `CreatorCommunityPostMenuBottomSheetDialog`를 열어 작성자는 수정/삭제/고정, 일반 사용자는 신고를 연결한다. 수정은 기존 `CreatorCommunityModifyActivity`, 삭제 확인은 `V2ModalDialog`, 신고 입력은 `CreatorCommunityReportDialog`를 재사용한다. - [x] **GREEN:** 상세 paywall의 `PurchaseCommunityPostDialog -> purchaseCommunityPost()` 연결은 유지하고 홈/커뮤니티 탭에는 구매 호출부가 없음을 확인한다. - [x] **GREEN:** 수정 Activity `RESULT_OK`와 고정 성공 시 상세를 재조회하고 상위 `RESULT_OK`를 설정하며, 삭제 event 수신 시 `RESULT_OK` 설정 후 상세를 종료한다. - [x] **GREEN 확인:** focused test, `./gradlew :app:mergeDebugResources :app:compileDebugKotlin`을 실행한다. - [x] **REFACTOR:** 신규 menu abstraction 또는 별도 popup을 만들지 않고 기존 BottomSheet/Dialog를 직접 재사용한다. - 검증 기록(2026-08-10): 신규 fields/methods/resource 미존재 RED를 확인한 뒤 권한 truth table, 고정·삭제·신고 성공/실패·중복 guard, 기존 dialog 재사용과 결과 전파를 구현했다. 커뮤니티 focused test와 resource/compile Gate가 PASS했다. #### Task 15.5 상위 새로고침 전파와 목록 메뉴 orphan 제거 **Goal 실행 `P15-T5`:** 상세 변경 결과가 홈·커뮤니티 탭을 함께 갱신하고 목록에 남은 관리 메뉴 코드를 제거한다. - **시작 조건:** `P15-T3`, `P15-T4` 완료. - **완료 증거:** 상세 `RESULT_OK -> CommunityChange.Updated -> 홈+커뮤니티 refresh` routing test PASS, 제거된 callback 관련 compile PASS. - **범위 밖:** 신고 후 새로고침, 다른 creator channel 탭. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChange.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChangeTest.kt` **Interfaces:** - Consumes: `resolveCommunityActivityResult(CommunityActivityResultSource.Detail, RESULT_OK) == CommunityChange.Updated`. - Produces: `CommunityChange.Updated` 처리 시 `homeActionDelegate?.refreshHome()`과 `refreshCreatorChannelCommunity()` 호출. - [x] **RED:** `CreatorChannelActivitySourceTest`에 상세 `Updated` 처리 블록이 홈/커뮤니티 refresh를 모두 호출한다는 routing 계약을 추가한다. - [x] **RED 확인:** 아래 명령으로 현재 `Updated`가 커뮤니티만 refresh하고 목록 전용 change가 남아 있어 실패함을 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" \ --tests "kr.co.vividnext.sodalive.v2.community.action.CommunityChangeTest" ``` - [x] **GREEN:** `handleCommunityChange(Updated)`가 홈과 커뮤니티를 함께 갱신하도록 변경한다. - [x] **GREEN:** 목록 owner-more 제거로 미사용이 된 `communityPostModifyLauncher`, `onCreatorChannelCommunityOwnerMoreClicked`, 목록용 고정/삭제/dialog 함수와 관련 import/repository 의존을 `CreatorChannelActivity`에서 제거한다. - [x] **GREEN:** 호출자가 사라진 `CommunityChange.Deleted`, `CommunityChange.PinChanged`를 제거하고 `CommunityChangeTest`의 삭제/고정 전용 케이스만 제거한다. `Write -> Created`, `Modify/Detail -> Updated`, 취소 -> `Ignored` 회귀는 유지한다. - [x] **GREEN 확인:** focused test와 `./gradlew :app:compileDebugKotlin`을 실행한다. - [x] **REFACTOR:** 다른 화면에서 사용 중인 `CommunityActionCommand.PostDetail`, `CommunityChange.Created/Updated`는 유지한다. - 검증 기록(2026-08-10): routing RED 2건을 확인한 뒤 상세 `RESULT_OK`의 `Updated` 처리에서 홈·커뮤니티를 함께 갱신하고 목록 메뉴 orphan을 제거했다. Activity/action focused test와 Kotlin compile이 PASS했다. #### Task 15.R1 목록 메뉴 제거 후 미사용 import 정리 **Goal 실행 `P15-R1`:** 확정 review 항목 `REV-P15-001`의 미사용 import를 제거해 Phase 15 style Gate를 회복한다. - **시작 조건:** `REV-P15-001` 확정, `P15-T5` 목록 메뉴/orphan 제거 완료. - **대상 파일:** `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` - **수정 범위:** `SharedPreferenceManager` 미사용 import 1줄 제거. - **회귀 테스트:** `./gradlew :app:ktlintCheck`, Phase 15 Gate 전체. - **완료 증거:** RED ktlint 정확한 1건 → import 제거 → ktlint/Gate PASS → review 보고서 수정 후 검증 누적. - **범위 밖:** 다른 import 순서, 관련 없는 style 정리, `.editorconfig` deprecation 경고. - [x] `./gradlew :app:ktlintCheck`에서 `CreatorChannelActivity.kt:29:1 Unused import`만 확정된 RED를 기록한다. - [x] `SharedPreferenceManager` import 1줄만 제거한다. - [x] `./gradlew :app:ktlintCheck`와 Phase 15 Gate를 재실행한다. - [x] `plan-task.md` Verification Log와 `phase15-community-purchase-detail-menu-review.md`에 완료 증거를 누적한다. - 검증 기록(2026-08-10): 최초 Gate에서 `CreatorChannelActivity.kt:29:1 Unused import` RED를 확인하고 해당 import 1줄만 제거했다. 최종 `ktlintCheck`와 Phase 15 Gate가 PASS했다. #### Task 15.R2 홈 reaction row binding 대상 정정 **Goal 실행 `P15-R2`:** 확정 review 항목 `REV-P15-002`의 잘못된 `ll_feed_community_reaction` id 위치를 정정해 유료 미구매 홈 item의 reaction UI만 숨긴다. - **시작 조건:** `REV-P15-002` 확정, `P15-T2` 표시 정책 구현 완료. - **대상 파일:** `app/src/main/res/layout/view_feed_community.xml` - **수정 범위:** `ll_feed_community_reaction` id를 작성자 row에서 하트·댓글 reaction row로 이동. - **회귀 검증:** `FeedItemTest`, `FeedViewTest`, `:app:mergeDebugResources`, `:app:compileDebugKotlin`, Phase 15 Gate, XML tree 정적 재확인. - **완료 증거:** 수정 전 XML tree에서 id가 프로필 row를 가리키는 근거 → id 1개 이동 → focused 회귀/Gate PASS → 리뷰 보고서 누적. - **TDD 예외 사유:** 저장소 규칙상 View visibility·layout 속성을 직접 검증하는 신규 테스트를 추가하지 않는다. 표시 분기는 기존 `FeedItemTest`, binding/resource는 resource merge·compile·정적 XML tree 검토로 대체한다. - **범위 밖:** 홈 card 간격·크기·제약, 다른 row 레이아웃. - [x] 수정 전 `ll_feed_community_reaction` id가 프로필 row에 있고 실제 reaction row에는 id가 없는 근거를 기록한다. - [x] id를 실제 reaction row로 이동한다. - [x] focused 회귀, resource merge/compile, Phase 15 Gate를 재실행한다. - [x] `plan-task.md` Verification Log와 review 보고서에 완료 증거를 누적한다. - 검증 기록(2026-08-10): 수정 전 id가 프로필 row에 연결되고 실제 reaction row에는 없음을 XML tree로 확인했다. id 1개를 실제 reaction row로 이동한 뒤 focused test, resource merge, Kotlin compile과 최종 Gate가 PASS했다. #### Task 15.R3 공유 FeedItem reaction 정책 범위 정정 **Goal 실행 `P15-R3`:** 확정 review 항목 `REV-P15-003`의 공유 모델 회귀를 제거해 크리에이터 채널 홈에서만 유료 미구매 reaction을 숨기고 다른 `FeedItem.Community` 호출부는 기존 표시 동작을 유지한다. - **시작 조건:** `REV-P15-003` 확정, `P15-T1`~`P15-T2` 표시 정책 구현 완료. - **대상 파일:** `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItem.kt`, `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt`, `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItemTest.kt`, `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - **수정 범위:** `FeedItem.Community` 신규 정책은 기본값으로 기존 reaction을 유지하고, 크리에이터 채널 홈 mapper만 잠금 시 숨김을 명시하도록 제한. - **회귀 테스트:** 잠금 item의 기본값은 reaction/comment 표시 유지, 크리에이터 채널 홈 opt-in은 reaction/comment 숨김, 기존 댓글 불가·고정 상태 테스트, Phase 15 Gate. - **완료 증거:** 메인 홈 인기 커뮤니티 mapper와 공유 `FeedCommunityView` 호출 경로 확인 → 기본값 회귀 테스트 RED → 최소 opt-in 구현 → focused 회귀/Gate PASS → 리뷰 보고서 누적. - **범위 밖:** 메인 홈 인기 커뮤니티의 이미지 잠금/가격 정책, 다른 feed UI 변경. - [x] 메인 홈 인기 커뮤니티가 `price`, `existOrdered`를 전달하고 공유 `FeedCommunityView.bind()`를 사용해 기존 reaction까지 숨겨지는 호출 경로를 기록한다. - [x] **RED:** 잠금 유료 `FeedItem.Community`가 신규 정책 기본값에서는 기존 reaction/comment 표시를 유지하고, opt-in에서는 둘 다 숨기는 회귀 테스트를 추가해 실패를 확인한다. - [x] **GREEN:** 신규 정책 기본값은 기존 표시를 유지하고 `CreatorChannelHomeSectionAdapter`만 잠금 reaction 숨김을 선택하도록 최소 수정한다. - [x] focused 회귀와 Phase 15 Gate를 재실행한다. - [x] `plan-task.md` Verification Log와 review 보고서에 완료 증거를 누적한다. - 검증 기록(2026-08-10): `HomeRecommendationMappers -> FeedItem.Community -> HomePopularCommunityAdapter -> FeedCommunityView.bind()` 호출 경로로 공유 정책 회귀를 확정했다. `hideReactionWhenLocked` 미존재 컴파일 RED 후 기본값 false, 크리에이터 채널 홈 true로 제한했고 `FeedItemTest`, 메인 홈 mapper 회귀 테스트와 최종 Gate가 PASS했다. #### Task 15.R4 메인 홈 회귀 테스트 파일 미사용 import 정리 **Goal 실행 `P15-R4`:** 확정 review 항목 `REV-P15-004`의 미사용 import를 제거해 Phase 15 style Gate를 회복한다. - **시작 조건:** `P15-R3` 메인 홈 회귀 테스트 추가 후 `ktlintCheck` 실패 확정. - **대상 파일:** `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - **수정 범위:** 실제 사용처가 없는 `ColorDrawable` import 1줄 제거. - **회귀 검증:** `./gradlew :app:ktlintCheck`, Phase 15 Gate, `git diff --check`. - **완료 증거:** ktlint 정확한 unused import 1건 → import 제거 → ktlint/Gate PASS → 리뷰 보고서 누적. - **범위 밖:** 같은 테스트 파일의 기존 구조·deprecated API 정리. - [x] `ktlintCheck`의 `HomeMainFragmentLayoutTest.kt:6:1 Unused import` RED를 기록한다. - [x] `ColorDrawable` import 1줄만 제거한다. - [x] `ktlintCheck`와 Phase 15 Gate를 재실행한다. - [x] `plan-task.md` Verification Log와 review 보고서에 완료 증거를 누적한다. - 검증 기록(2026-08-10): 메인 홈 회귀 테스트 추가 후 lint 대상이 된 파일에서 사용처 없는 `ColorDrawable` import 1건을 확인해 제거했다. 최종 `ktlintCheck`와 전체 focused test Gate가 PASS했다. #### Phase 15 Gate **Goal 실행 `P15-GATE`:** 승인된 홈/탭/상세 사용자 흐름과 회귀 방지를 최종 판정한다. - **시작 조건:** `P15-T1`~`P15-T5` 완료. - **완료 증거:** 아래 명령을 순차 실행해 모두 exit code 0을 확인하고 각 Task 검증 기록 및 `Verification Log`에 실제 결과를 누적한다. - **범위 밖:** `app/src/androidTest`, `connectedDebugAndroidTest`, 기기·에뮬레이터 조작, 스크린샷·시각 QA, Git commit. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedItemTest" \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeModelsTest" \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*" \ --tests "kr.co.vividnext.sodalive.v2.community.action.*" ./gradlew :app:testDebugUnitTest --tests \ "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" ./gradlew :app:mergeDebugResources ./gradlew :app:compileDebugKotlin ./gradlew :app:ktlintCheck git diff --check ``` **Expected:** 홈 Response 신규 필드와 reaction/pin 정책, list/grid 상세 이동 전용화, 상세 더보기 권한·구매/관리/신고, 상세 변경 후 홈+커뮤니티 refresh 관련 focused test PASS. 리소스 병합·Kotlin 컴파일·ktlint·diff whitespace 검증 PASS. #### Phase 15 실행 순서 ```text P15-T1 → P15-T2 → P15-T3 → P15-T4 → P15-T5 → P15-R1 → P15-R2 → P15-R3 → P15-R4 → P15-GATE ``` #### Phase 15 계획 기록 — 2026-08-10 - 상태: 계획 완료 / 구현 미시작 - 무엇을: 승인된 PRD `F0-20260810`을 `P15-T1`~`P15-T5`, `P15-GATE`로 분해했다. - 왜: 홈·커뮤니티 탭의 표시/액션 일관성과 상세 구매·메뉴·결과 전파를 구현 전에 검증 가능한 단위로 고정하기 위해서다. - 어떻게: - `git diff --check` — 문서 변경 whitespace 검증 PASS. - `./gradlew tasks --all` — exit code 0, `BUILD SUCCESSFUL`. - 남은 항목: Phase 15 구현 전체. 사용자 지시에 따라 이번 작업에서는 수행하지 않는다. - 다음 행동: 사용자가 구현을 별도로 요청하면 `P15-T1` RED부터 순차 실행한다. #### Phase 15 구현·검증 기록 — 2026-08-10 - 상태: 구현 및 검증 완료 / Git commit 미수행 - 무엇을: `P15-T1`~`P15-T5`를 구현하고 리뷰 확정 항목 `P15-R1`~`P15-R4`를 수정했다. - 왜: 미체크 항목이 구현 누락이었음을 코드·테스트 대조로 확인했고, 구현 후 독립 리뷰와 Gate에서 실제 회귀·style 문제를 확인했기 때문이다. - 어떻게: - 홈 Response에 `isPinned`, `isCommentAvailable`을 연결하고 크리에이터 채널 홈만 유료 미구매 reaction 숨김을 opt-in했다. - 홈·커뮤니티 탭 item 액션을 상세 이동으로 제한하고 상세에 구매·권한별 더보기·변경 결과 전파를 모았다. - `REV-P15-001`~`REV-P15-004`를 각각 회귀 Task로 전환한 뒤 최소 수정했다. - 전체 focused local unit/source contract test, resource merge, Kotlin compile, ktlint를 최종 working tree에서 재실행해 `BUILD SUCCESSFUL`을 확인했고 `git diff --check`, `git diff HEAD --check`는 출력 없이 PASS했다. - 제외: 저장소 규칙과 사용자 지시에 따라 `app/src/androidTest`, 기기·에뮬레이터 조작, 스크린샷·시각 QA, Git commit은 수행하지 않았다. - 남은 항목: 실제 서버 Response 배포 상태와 기기 수동 UI 확인은 이번 자동 검증 범위 밖이다. ## Phase 16 — 홈·커뮤니티 리스트 권한별 더보기 메뉴 **Phase 결과:** 상세와 동일한 권한별 게시글 더보기 메뉴를 홈 커뮤니티 item과 커뮤니티 탭 리스트형에서 사용할 수 있고, 성공한 관리 작업은 두 목록에 함께 반영된다. **선행조건:** 승인된 PRD `Home and Community List More Menu Requirements`, Phase 15 구현·검증 완료. **Phase 완료 조건:** `P16-T1`~`P16-T3`, `P16-GATE` 완료와 Task별 검증 기록 및 `Verification Log` 누적. ### 범위 **포함** - 홈 커뮤니티 item과 커뮤니티 탭 리스트형 작성자 영역 우측 끝 `ic_seemore_vertical` 버튼. - 상세와 동일한 작성자/무료/구매 완료/유료 미구매 권한 truth table. - 작성자 수정·삭제·고정/고정 해제, 일반 사용자 신고. - 성공한 수정·삭제·고정/고정 해제 후 홈+커뮤니티 새로고침. - card 본문 상세 이동과 더보기 메뉴 click 분리. **제외** - 커뮤니티 탭 썸네일형(그리드형) 더보기 버튼. - 목록에서 유료 게시글 구매·오디오 재생. - 상세 메뉴 구조 변경, 레거시 파일 수정, 신규 범용 coordinator/dependency. - `app/src/androidTest`, 기기·에뮬레이터 조작, 스크린샷·시각 QA, Git commit. ### Task 16.1 공용 메뉴 권한 presentation 계약 **Goal 실행 `P16-T1`:** 홈과 커뮤니티 리스트가 동일한 메뉴 payload와 권한 계산을 사용하고 다른 공유 feed는 기존 표시를 유지한다. - **시작 조건:** PRD 권한 truth table 승인. - **완료 증거:** 메뉴 권한 4개 케이스와 홈/커뮤니티 mapper focused test PASS. - **범위 밖:** 버튼 layout/click, Activity menu/API 실행. **Files:** - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMenuItem.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityUiModels.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/model/CreatorChannelCommunityMappers.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeUiModels.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/model/CreatorChannelHomeMappers.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItem.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityMapperTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeMapperTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItemTest.kt` **Interfaces:** ```kotlin data class CreatorChannelCommunityMenuItem( val postId: Long, val creatorId: Long, val currentUserId: Long, val price: Int, val existOrdered: Boolean, val isPinned: Boolean ) { val isOwner: Boolean val showMore: Boolean } ``` - `isOwner = creatorId == currentUserId` - `showMore = isOwner || price == 0 || existOrdered` - `CreatorChannelCommunityPostUiModel`은 `menuItem: CreatorChannelCommunityMenuItem`을 제공한다. - `CreatorChannelHomeSection.Communities`는 `currentMemberId: Long`을 함께 보존해 adapter가 같은 menu item을 만든다. - `FeedItem.Community`는 `showMore: Boolean = false`를 추가해 다른 공유 feed 호출부의 기존 동작을 유지한다. - [x] **RED:** `CreatorChannelCommunityMapperTest`에 작성자 유료 미구매=true, 일반 무료=true, 일반 유료 구매=true, 일반 유료 미구매=false인 `menuItem.showMore`와 `isPinned` 보존 케이스를 추가한다. - [x] **RED:** `CreatorChannelHomeMapperTest`에 `Communities.currentMemberId` 보존, `FeedItemTest`에 `showMore` 기본값 false 회귀를 추가한다. - [x] **RED 확인:** 아래 명령을 실행해 신규 type/property 미존재 컴파일 실패를 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityMapperTest" \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeMapperTest" \ --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedItemTest" ``` - [x] **GREEN:** 위 `CreatorChannelCommunityMenuItem` 계산 property를 추가하고 커뮤니티 mapper/UI model과 홈 `Communities.currentMemberId`를 연결한다. - [x] **GREEN:** `FeedItem.Community.showMore`를 기본값 false로 추가한다. - [x] **GREEN 확인:** 같은 focused test가 PASS하는지 확인한다. - [x] **REFACTOR:** 별도 권한 service/factory를 만들지 않고 menu item 계산 property 하나만 공용 정책으로 유지한다. - 검증 기록(2026-08-10): `menuItem`, `currentMemberId`, `showMore` 미존재로 `compileDebugUnitTestKotlin` RED 실패를 확인했다. `CreatorChannelCommunityMenuItem` 계산 property와 커뮤니티/홈 mapper 연결, `FeedItem.Community.showMore=false` 기본값을 최소 구현한 뒤 지정된 focused test 26개가 모두 PASS했다. 명세·코드 품질 독립 검토에서도 차단 이슈가 없음을 확인했다. ### Task 16.2 홈·커뮤니티 리스트 더보기 버튼과 callback **Goal 실행 `P16-T2`:** 홈 커뮤니티 item과 커뮤니티 리스트형의 더보기 버튼이 card 상세 이동과 분리된 공용 menu callback을 호출한다. - **시작 조건:** `P16-T1` 완료. - **완료 증거:** 홈/리스트 callback routing focused test, resource merge, Kotlin compile PASS와 grid 무변경 확인. - **범위 밖:** BottomSheet 표시와 mutation API. - **TDD 예외 사유:** 버튼 위치·constraint·visibility 속성 자체는 저장소 규칙상 자동 테스트에서 직접 검증하지 않는다. - **대체 검증 방법:** 권한 visibility는 `P16-T1` 순수 로직 test, callback은 click/source contract test, UI resource는 id 존재·resource merge·compile·정적 XML tree 대조로 검증한다. **Files:** - Modify: `app/src/main/res/layout/view_feed_community.xml` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/ui/CreatorChannelHomeSectionAdapter.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelHomeFragment.kt` - Modify: `app/src/main/res/layout/item_creator_channel_community_list.xml` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityListAdapter.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragment.kt` - Verify only: `app/src/main/res/layout/item_creator_channel_community_grid.xml` - Verify only: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/community/ui/CreatorChannelCommunityGridAdapter.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedViewTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/community/CreatorChannelCommunityFragmentLayoutTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` **Interfaces:** ```kotlin fun FeedCommunityView.setOnMoreClick(listener: ((FeedItem.Community) -> Unit)?) CreatorChannelHomeSectionAdapter( onCommunityClick: (Long) -> Unit, onCommunityMoreClick: (CreatorChannelCommunityMenuItem) -> Unit ) CreatorChannelCommunityListAdapter( onPostClick: (CreatorChannelCommunityPostUiModel) -> Unit, onMoreClick: (CreatorChannelCommunityMenuItem) -> Unit ) fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem) ``` - [x] **RED:** `FeedViewTest`에 `btn_feed_community_more` resource와 `setOnMoreClick`이 bound item만 전달하는 callback 케이스를 추가한다. - [x] **RED:** `CreatorChannelCommunityFragmentLayoutTest`와 `CreatorChannelActivitySourceTest`에 홈/리스트 adapter → Fragment Host의 동일 menu callback, list root 상세 click 유지, grid menu callback 부재 계약을 추가한다. - [x] **RED 확인:** 아래 명령으로 신규 resource/API/callback 미존재 실패를 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedViewTest" \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.CreatorChannelCommunityFragmentLayoutTest" \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" ``` - [x] **GREEN:** `view_feed_community.xml` 작성자 row를 전체 폭으로 유지하고 우측 끝에 42dp `btn_feed_community_more`(`ic_seemore_vertical`, `read_more`)를 추가한다. `FeedCommunityView.bind()`는 `item.showMore`를 bind하고 별도 more click을 적용한다. - [x] **GREEN:** 홈 adapter는 `Communities.currentMemberId`로 menu item을 만들고 `FeedItem.showMore`, `setOnMoreClick`, `onCommunityMoreClick`을 연결한다. HomeFragment Host로 같은 menu item을 전달한다. - [x] **GREEN:** 커뮤니티 리스트 layout의 기존 top actions 끝에 42dp more 버튼을 추가하고 wrapper는 `showOwnerTopPrice || menuItem.showMore`일 때 표시한다. list adapter/Fragment Host에 `onMoreClick`을 연결한다. - [x] **GREEN:** root `onCommunityClick`/`onPostClick`은 상세 이동을 유지하고 more 버튼은 메뉴 callback만 호출한다. - [x] **GREEN:** grid layout/adapter에는 more id/callback을 추가하지 않는다. - [x] **GREEN 확인:** focused test와 `./gradlew :app:mergeDebugResources :app:compileDebugKotlin`이 PASS하는지 확인한다. - [x] **REFACTOR:** 이번 Task가 만든 listener/import/binding 중복만 정리하고 공유 View의 다른 호출부 동작은 변경하지 않는다. - 검증 기록(2026-08-10): `btn_feed_community_more`, `btn_creator_channel_community_list_more`, `setOnMoreClick` 미존재로 `compileDebugUnitTestKotlin` RED 실패를 확인했다. 두 42dp 버튼과 홈/리스트의 동일 `CreatorChannelCommunityMenuItem` Host callback을 최소 연결한 뒤 지정된 focused test 119개가 모두 PASS했다. `:app:mergeDebugResources :app:compileDebugKotlin`도 PASS했으며 grid XML/adapter에는 more id·callback이 없음을 정적 확인했다. ### Task 16.3 Activity 공용 메뉴와 mutation 결과 전파 **Goal 실행 `P16-T3`:** 홈과 커뮤니티 리스트의 공용 callback이 상세와 같은 BottomSheet/Dialog/API를 실행하고 성공한 관리 작업만 두 목록을 갱신한다. - **시작 조건:** `P16-T2` 완료, Phase 15 상세 메뉴/API 계약 유지. - **완료 증거:** Activity source/routing focused test와 전체 community 회귀, compile PASS. - **범위 밖:** 상세 ViewModel/Activity 메뉴 변경, 신고 후 목록 새로고침. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt` - Reuse: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/data/CreatorChannelRepository.kt` - Reuse: 레거시 `CreatorCommunityPostMenuBottomSheetDialog`, `CreatorCommunityReportDialog`, `CreatorCommunityModifyActivity`, `CreatorCommunityRepository` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivitySourceTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/community/action/CommunityChangeTest.kt` **Interfaces:** ```kotlin override fun onCreatorChannelCommunityMoreClicked(item: CreatorChannelCommunityMenuItem) private fun updateCreatorChannelCommunityPostFixed(item: CreatorChannelCommunityMenuItem) private fun deleteCreatorChannelCommunityPost(item: CreatorChannelCommunityMenuItem) private fun reportCreatorChannelCommunityPost(item: CreatorChannelCommunityMenuItem, reason: String) ``` - Activity는 `CreatorCommunityRepository`와 기존 `CreatorChannelRepository.reportCommunityPost()`를 주입해 재사용한다. - 수정 launcher의 `RESULT_OK`, 삭제·고정 성공은 기존 `handleCommunityChange(CommunityChange.Updated)`로 홈+커뮤니티를 갱신한다. - 신고 성공은 API message 또는 `character_comment_report_submitted` Toast만 표시하고 새로고침하지 않는다. - `isCommunityPostMutating` 단일 guard로 고정·삭제·신고 중복 요청을 막고 성공/실패 모두 guard를 해제한다. - [x] **RED:** `CreatorChannelActivitySourceTest`에 HomeFragment.Host와 CommunityFragment.Host의 동일 override 하나, 권한 guard, 기존 BottomSheet/Dialog/modify launcher 연결 계약을 추가한다. - [x] **RED:** 같은 test에 고정 `!item.isPinned`, 삭제 `ModifyCommunityPostRequest(isActive=false)`, 신고 `reportCommunityPost`, 성공 `CommunityChange.Updated`, 신고 성공 no-refresh, 중복 guard 계약을 추가한다. - [x] **RED 확인:** 아래 명령으로 Activity 공용 메뉴/mutation 연결 미존재 실패를 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" \ --tests "kr.co.vividnext.sodalive.v2.community.action.CommunityChangeTest" ``` - [x] **GREEN:** Activity에 modify launcher와 `CreatorCommunityPostMenuBottomSheetDialog`를 연결하고 `item.showMore`가 false면 menu를 시작하지 않는다. - [x] **GREEN:** 작성자 menu는 수정·삭제·고정/고정 해제, 일반 사용자 menu는 `CreatorCommunityReportDialog` 신고만 연결한다. mutation method는 메뉴 역할과 `item.isOwner`를 다시 확인한다. - [x] **GREEN:** 고정은 `updateCommunityPostFixed(item.postId, !item.isPinned, authToken())`, 삭제는 `ModifyCommunityPostRequest(creatorCommunityId = item.postId, isActive = false)` JSON `text/plain` 요청, 신고는 `CreatorChannelRepository.reportCommunityPost(item.postId, reason, authToken())`를 호출한다. - [x] **GREEN:** 수정 복귀·고정·삭제 성공만 `CommunityChange.Updated`로 홈+커뮤니티를 갱신하고 실패/신고는 목록 상태를 변경하지 않는다. - [x] **GREEN 확인:** focused test, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`이 PASS하는지 확인한다. - [x] **REFACTOR:** 상세 메뉴 코드를 옮기거나 신규 coordinator를 만들지 않고 Activity의 공용 목록 처리만 유지한다. - 검증 기록(2026-08-10): Activity 공용 메뉴와 mutation source 계약 3개를 먼저 추가한 뒤 focused test에서 기존 100개는 통과하고 신규 3개가 `item.showMore` guard 및 mutation method/guard 미존재로 RED 실패함을 확인했다. 기존 BottomSheet/Dialog/modify launcher와 legacy/V2 repository를 Activity에 직접 연결한 뒤 focused test 103개, community wildcard 회귀 86개가 모두 PASS했다. `:app:compileDebugKotlin`도 PASS했으며 수정·고정·삭제 성공만 `CommunityChange.Updated`를 호출하고 신고 성공/모든 실패 경로에는 refresh가 없음을 source test로 확인했다. - 리뷰 회귀 수정 검증 기록(2026-08-10): 게시물 작성자 권한은 채널 소유자 상태와 독립된 `item.isOwner`라는 PRD 계약에 맞춰 source test를 먼저 변경했고, 현재 `isCreatorChannelOwner()` 추가 조건 때문에 focused test 103개 중 권한 계약 3개가 RED 실패함을 확인했다. 수정·고정·삭제·신고 경로에서 채널 소유자 조건만 제거한 뒤 focused test 103개와 community wildcard 회귀 86개가 모두 PASS했고 `:app:compileDebugKotlin`도 PASS했다. ### Task 16.R1 Phase 완료 문서 정합성 복구 **Goal 실행 `P16-R1`:** 확정 review 항목 `REV-P16-001`에 따라 Phase 16 구현·Gate 완료 상태와 검증 증거를 문서에 누적한다. - **시작 조건:** `P16-T1`~`P16-T3`, `P16-GATE` 실행 완료와 `REV-P16-001` 확정. - **대상 파일:** `docs/20260708_커뮤니티_게시물_상세_댓글_답글/plan-task.md`, `reviews/phase16-home-community-list-more-menu-review.md`. - **수정 범위:** 기존 계획 기록은 보존하고 구현·Gate 완료 기록, `Verification Log`, 리뷰 판정만 누적한다. - **회귀 검증:** `git diff --check`, 최종 리뷰 재검토. - **완료 증거:** 미시작 상태와 Gate 기록 누락 근거 → 완료 기록 누적 → diff check PASS → 5개 리뷰 lane 최종 판정. - **범위 밖:** production/test 코드 변경, Gate 재실행을 위한 테스트 완화, Git commit. - [x] Phase 16 구현·검증 완료 기록을 기존 계획 기록 뒤에 누적한다. - [x] `Verification Log`에 focused test, resource merge, compile, ktlint, diff check 결과와 제외 범위를 누적한다. - [x] Phase 16 리뷰 보고서에 `REV-P16-001` 수정 및 재검토 결과를 누적한다. - [x] `git diff --check`와 최종 리뷰 재검토를 통과한다. - 검증 기록(2026-08-10): Phase 16 완료 기록과 `Verification Log`를 누적한 뒤 `git diff --check`, `git diff HEAD --check`가 출력 없이 PASS했다. 최종 재검토에서 목표·실행 QA·코드 품질·보안·맥락 5개 lane이 모두 PASS했으며 추가 차단 이슈가 없음을 확인했다. ### Phase 16 Gate **Goal 실행 `P16-GATE`:** 권한별 버튼·메뉴·mutation·새로고침과 grid 제외, 기존 상세/구매 회귀를 최종 판정한다. - **시작 조건:** `P16-T1`~`P16-T3` 완료. - **완료 증거:** 아래 명령의 exit code 0, 정적 권한/클릭 대조, Task별 검증 기록과 `Verification Log` 누적. - **범위 밖:** Gate 통과를 위한 test 완화, 기기 UI 조작, Git commit. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedItemTest" \ --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedViewTest" \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeMapperTest" \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelActivitySourceTest" ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*" \ --tests "kr.co.vividnext.sodalive.v2.community.action.*" ./gradlew :app:mergeDebugResources ./gradlew :app:compileDebugKotlin ./gradlew :app:ktlintCheck git diff --check git diff HEAD --check ``` **Expected:** 홈·커뮤니티 리스트형은 동일 권한 메뉴를 제공하고 card root 상세 이동을 유지한다. grid에는 메뉴가 없고, 미구매 유료 일반 사용자는 버튼이 숨겨진다. 수정·삭제·고정 성공은 홈+커뮤니티를 갱신하며 신고/실패는 갱신하지 않는다. 기존 상세 구매·메뉴 focused 회귀와 resource/compile/style/whitespace Gate가 모두 PASS한다. ### Phase 16 실행 순서 ```text P16-T1 → P16-T2 → P16-T3 → P16-GATE ``` ### Phase 16 계획 기록 — 2026-08-10 - 상태: 계획 완료 / 구현 미시작 - 무엇을: 승인된 홈·커뮤니티 리스트형 권한별 더보기 후속 요구를 `P16-T1`~`P16-T3`, `P16-GATE`로 분해했다. - 왜: 공용 권한 계산 → UI callback → Activity mutation 순으로 계약을 분리해 grid 제외와 상세 이동 회귀를 검증하기 위해서다. - 남은 항목: 사용자 계획 검토 승인 후 `P16-T1` RED부터 순차 실행한다. - Git commit: 사용자 지시에 따라 수행하지 않는다. ### Phase 16 구현·검증 기록 — 2026-08-10 - 상태: 구현 및 Gate 완료 / Git commit 미수행 - 무엇을: `P16-T1`~`P16-T3`를 구현하고 리뷰 확정 항목 `REV-P16-001`을 `P16-R1`로 수정했다. - 왜: 홈과 커뮤니티 리스트형에서 상세과 동일한 권한별 메뉴를 제공하고, 성공한 관리 작업만 양쪽 목록에 반영하기 위해서다. - 어떻게: - 공용 `CreatorChannelCommunityMenuItem`으로 작성자/가격/구매 권한과 고정 상태를 전달했다. - 홈·리스트의 더보기 click을 card 상세 click과 분리하고 grid는 기존 상세 이동만 유지했다. - 기존 BottomSheet/Dialog/repository를 Activity에서 재사용하고 수정·삭제·고정 성공만 `CommunityChange.Updated`로 전파했다. - 최초 독립 명세 검토에서 채널 소유자 조건이 작성자 권한을 제한하는 문제를 확인해 `item.isOwner` 계약으로 회귀 수정했다. - 최종 Gate의 focused test, resource merge, Kotlin compile, ktlint, 두 diff check가 모두 통과했다. - 제외: `app/src/androidTest`, 기기·에뮬레이터 조작, 스크린샷·시각 QA, 실제 서버 mutation, 별도 worktree, Git commit. - 남은 항목: 없음. ## Verification Log - 2026-08-11: 사용자 요청에 따라 Phase 15·16 완료 체크를 PRD, production, test와 다시 역대조했다. 실제 Task 체크박스는 모두 완료 상태였고 체크 누락·미구현 항목은 없었다. Phase 15 홈 Response/공유 feed 회귀 test, Phase 16 홈·Feed·Activity focused test, community/action focused test와 필터 없는 전체 `:app:testDebugUnitTest`가 모두 `BUILD SUCCESSFUL`로 PASS했다. `:app:mergeDebugResources`, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check`, `git diff HEAD --check`도 PASS했다. 구매 호출은 상세에만 있고, 홈·리스트 root/more 분리와 grid 제외, 권한별 메뉴, 성공 관리 mutation의 홈+커뮤니티 갱신, 신고·실패 no-refresh, 공유 `FeedItem.Community` 기본값 보존을 정적으로 확인했다. 독립 재리뷰도 Critical/Important/Minor 이슈 없음과 승인 가능으로 판정했다. 새 확정 결함이 없어 억지로 회귀 수정 Task나 production/test 변경을 추가하지 않았고 Git commit도 수행하지 않았다. - 2026-08-10: Phase 16 `P16-T1`~`P16-T3`와 `P16-GATE`를 완료했다. 최종 working tree에서 홈·Feed·Activity focused test와 community/action 회귀 test가 `BUILD SUCCESSFUL`로 PASS했고, `:app:mergeDebugResources`, `:app:compileDebugKotlin`, `:app:ktlintCheck`도 모두 PASS했다. `git diff --check`, `git diff HEAD --check`는 출력 없이 통과했다. 허용된 실행 QA 재검증에서도 P0 시나리오 11/11과 focused 128개, community/action 96개가 PASS했다. 리뷰에서 확인된 작성자 권한 추가 제한은 `item.isOwner` 기준으로 회귀 수정했고, 완료 문서 누락 `REV-P16-001`은 `P16-R1`로 누적했다. 저장소 규칙과 사용자 지시에 따라 `app/src/androidTest`, 기기·에뮬레이터 조작, 스크린샷·시각 QA, 실제 서버 mutation, 별도 worktree, Git commit은 수행하지 않았다. `.editorconfig disabled_rules` 및 Gradle deprecated feature 경고는 기존 비차단 경고로 유지했다. - 2026-08-10: Phase 15 문서 대조·구현·독립 재리뷰를 완료했다. 미구현 체크리스트는 `P15-T1`~`P15-T5`로 구현했고, 리뷰에서 `REV-P15-001` 미사용 import, `REV-P15-002` reaction id 오배치, `REV-P15-003` 공유 피드 reaction 정책 범위 회귀, `REV-P15-004` 테스트 파일 미사용 import를 확정해 `P15-R1`~`P15-R4`로 수정했다. 최종 working tree에서 홈/공유 피드·커뮤니티 상세/action·상위 Activity focused test, `:app:mergeDebugResources`, `:app:compileDebugKotlin`, `:app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`로 PASS했고 `git diff --check`, `git diff HEAD --check`는 출력 없이 PASS했다. 수정 후 독립 재리뷰에서도 새 Critical/Important/Minor 이슈가 없음을 확인했다. `.editorconfig disabled_rules` 및 기존 test deprecated API 경고는 실패가 아닌 기존 경고이며 범위 밖으로 유지했다. Git commit은 수행하지 않았다. - 2026-08-10: 승인된 후속 PRD와 Phase 15 구현 계획만 작성했다. placeholder·요구사항 충돌을 자체 검토했고 `git diff --check`, `./gradlew tasks --all`이 통과했다. 사용자 지시에 따라 production/test 코드 구현과 Git commit은 수행하지 않았다. - 2026-07-12: Phase 14 유료 미구매 상세 게시글 reaction/comment 숨김 변경을 완료했다. RED는 `CreatorChannelCommunityDetailViewModelTest.유료 미구매 게시물은 작성자가 아니면 reaction 현황과 댓글을 숨긴다`의 `showReaction` 미구현 컴파일 실패로 확인했고, 구현 후 focused 테스트, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`로 PASS했다. 실제 화면 직접 조작은 Activity가 내부 진입 경로와 데이터 계정에 의존해 이번 검증에서는 단위/리소스/컴파일/스타일 검증으로 대체했다. - 구현 전 문서 작성 단계에서는 Gradle 검증을 실행하지 않았다. - 2026-07-12: 커뮤니티 상세 오디오 중앙 버튼 및 유료 게시글 구매 후속 변경을 반영했다. 상세 ViewModel 구매 회귀 테스트, 상세 UI 계약 테스트, 커뮤니티 targeted 테스트, 리소스 병합, Kotlin 컴파일, ktlint, diff whitespace 검증이 모두 통과했다. 최초 병렬 Gradle 실행 중 resource packaging incremental output 충돌성 오류가 한 번 발생했으나 순차 재실행에서 재현되지 않았다. - 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 파일 권한 문제로 실패했으나, 동일 명령을 권한 상승 재실행해 통과했다. - 2026-07-09: 사용자 후속 요청 4건을 반영했다. 상세/답글 titlebar의 `커뮤니티`, `답글` 문구를 제거했고, 댓글/답글 더보기 메뉴 문구를 `수정하기`, `삭제하기`로 변경했다. v2 패키지 내부 삭제 확인은 `V2ModalDialog`로 교체했으며, 수정은 Dialog 없이 하단 입력창 수정 모드로 전환되도록 구현했다. 수정 모드에서는 send 버튼으로 수정 API를 호출하고, send 버튼 오른쪽 X 버튼으로 수정 취소가 가능하다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.community.*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`가 모두 통과했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation 경고는 기존 설정 경고로 남아 있다. - 2026-07-09: 댓글/답글 더보기 메뉴 font 후속 요청을 반영했다. `PopupMenu`는 메뉴 item font를 직접 지정하기 어려워, 댓글/답글 더보기만 `PopupWindow` 기반 `CreatorChannelCommunityMorePopup`으로 교체하고 메뉴 TextView에 `android:fontFamily="@font/regular"`를 명시했다. 검증 명령은 Phase 10.1 기록에 누적한다. - 2026-07-09: 수정하기 터치 시 키보드가 바로 올라오고, 입력/수정 API success 후 키보드가 내려가야 한다는 후속 요청을 반영했다. 상세/답글 Activity에서 수정 모드 진입 시 입력창 focus와 IME show를 수행하고, 작성/수정 성공 이벤트에서 IME hide와 focus clear를 수행한다. 검증 명령은 Phase 11.1 기록에 누적한다. - 2026-07-12: 커뮤니티 상세 상단 게시글 영역의 rounded card 배경 제거와 게시글-댓글/댓글 사이 1dp border 후속 요청을 반영했다. 검증 명령은 Phase 12.1 기록에 누적한다.