diff --git a/docs/20260708_커뮤니티_게시물_상세_댓글_답글/plan-task.md b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/plan-task.md index ed253252..98391647 100644 --- a/docs/20260708_커뮤니티_게시물_상세_댓글_답글/plan-task.md +++ b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/plan-task.md @@ -285,7 +285,694 @@ - 검증 기록: - 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 검증을 실행하지 않았다. diff --git a/docs/20260708_커뮤니티_게시물_상세_댓글_답글/prd.md b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/prd.md index 7062dd06..f65a489c 100644 --- a/docs/20260708_커뮤니티_게시물_상세_댓글_답글/prd.md +++ b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/prd.md @@ -11,6 +11,10 @@ - 사용자는 게시글에 좋아요를 누르고, 댓글과 답글을 작성/수정/삭제하고 싶어 한다. - 댓글 작성이 불가능한 게시글에서는 댓글 영역과 입력 영역이 노출되면 안 된다. - 조회용 신규 v2 API 계약과 Figma 기반 UI 요구사항이 별도 문서로 정의되어야 한다. +- 2026-08-10 후속 확인에서 홈 커뮤니티 섹션의 유료 미구매 게시글에 하트·댓글 UI가 노출되고, 홈 Response에 고정/댓글 가능 여부가 없어 커뮤니티 탭·상세와 표시 상태가 달라지는 문제가 확인됐다. +- 커뮤니티 탭의 유료 미구매 가격 UI와 구매 진입점이 홈·상세와 일치하지 않고, 상세 구매 결과가 커뮤니티 탭에만 반영되어 홈 섹션은 구매 전 상태로 남는다. +- 게시글 관리/신고 메뉴가 상세에 없고 목록 item에 별도 액션이 남아 있어, 목록은 상세 이동만 담당하고 구매·관리 액션은 상세에 모으는 정리가 필요하다. +- 2026-08-10 구현 확인 후 상세에만 둔 권한별 더보기 메뉴를 홈 커뮤니티 item과 커뮤니티 탭 리스트형에서도 바로 사용할 수 있어야 한다는 후속 요구가 확정됐다. --- @@ -34,6 +38,11 @@ - 하트 터치 시 좋아요 API를 호출하고, `isLiked`에 따라 `ic_feed_community_heart_fill`/`ic_feed_community_heart` icon을 표시한다. - 댓글 작성이 불가능한 게시글(`isCommentAvailable == false`)이면 댓글 목록과 하단 입력 영역을 숨긴다. - 하단 댓글/답글 입력 영역은 화면 하단에 고정하고 키보드 표시 여부에 따라 함께 올라오게 한다. +- 크리에이터 채널 홈 Response의 커뮤니티 게시글에 `isPinned`, `isCommentAvailable`을 추가하고 홈 섹션의 고정·댓글 표시 상태에 반영한다. +- 홈과 커뮤니티 탭의 게시글 card 본문 터치는 상세 이동만 수행하고, 별도 더보기 버튼은 권한별 관리/신고 메뉴를 제공한다. 유료 게시글 구매는 상세에서만 시작한다. +- 커뮤니티 탭 리스트형·썸네일형의 유료 미구매 가격 UI를 홈·상세와 같은 잠금/가격 표현으로 통일한다. +- 상세·홈·커뮤니티 리스트형 게시글 작성자 영역 우측 끝에 `ic_seemore_vertical` 버튼을 표시하고 작성자/구매 권한에 따라 관리 또는 신고 메뉴를 제공한다. +- 상세 구매·수정·삭제·고정 결과가 크리에이터 채널 홈과 커뮤니티 탭에 모두 반영되도록 결과 전파 범위를 확장한다. --- @@ -45,6 +54,8 @@ - 레거시 커뮤니티/댓글 관련 파일을 직접 수정하지 않는다. - 크리에이터 채널의 다른 탭 UI나 공통 스크롤 구조를 재설계하지 않는다. - v2 패키지 내부 popup dialog는 `kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog`를 사용한다. +- 레거시 커뮤니티 게시글 목록의 구매 동작을 변경하지 않는다. +- 커뮤니티 게시글 화면 공통화를 위한 신규 framework, coordinator 또는 범용 abstraction을 추가하지 않는다. --- @@ -64,11 +75,96 @@ - 사용자는 내가 쓴 댓글/답글을 수정하거나 삭제하고 싶다. - 채널 크리에이터는 자신의 채널 댓글/답글을 삭제해 관리하고 싶다. - 사용자는 댓글이 불가능한 게시글에서는 입력창 없이 게시글만 보고 싶다. +- 사용자는 홈과 커뮤니티 탭에서 게시글을 터치하면 다른 중간 동작 없이 상세로 이동하고 싶다. +- 사용자는 미구매 유료 게시글의 하트·댓글 정보를 홈에서 보지 않고 상세에서 구매하고 싶다. +- 사용자는 고정 게시글을 홈 커뮤니티 섹션에서도 구분하고 싶다. +- 작성자는 상세에서 자신의 게시글을 수정·삭제·고정/고정 해제하고 싶다. +- 일반 사용자는 무료 또는 구매 완료 게시글 상세에서 게시글을 신고하고 싶다. +- 작성자는 홈 또는 커뮤니티 리스트에서 자신의 게시글을 수정·삭제·고정/고정 해제하고 싶다. +- 일반 사용자는 홈 또는 커뮤니티 리스트의 무료·구매 완료 게시글을 신고하고 싶다. --- ## 7. Core Features +### F0-20260810. 구매 진입점·표시 상태·권한별 메뉴 후속 정리 +홈·커뮤니티 탭은 게시글 상태를 일관되게 표시하고 card 본문에서 상세로 이동한다. 구매는 상세에서만 수행하며, 권한별 게시글 관리/신고 메뉴는 상세·홈·커뮤니티 리스트형에서 제공한다. + +#### Home Response Contract +크리에이터 채널 홈의 `communities`와 `notices`가 사용하는 게시글 Response에 상세와 같은 필드를 추가한다. + +```kotlin +data class CreatorChannelCommunityPostResponse( + // 기존 필드 유지 + @SerializedName("isPinned") val isPinned: Boolean, + @SerializedName("isCommentAvailable") val isCommentAvailable: Boolean +) +``` + +#### Home Community Requirements +- `isPinned == true`이면 커뮤니티 탭과 같은 고정/공지 표시를 노출한다. +- `price > 0 && existOrdered == false`인 유료 미구매 게시글은 하트 icon·좋아요 수·댓글 icon·댓글 수 reaction UI 전체를 숨긴다. +- 무료 또는 구매 완료 게시글은 하트 icon과 좋아요 수를 표시한다. +- 무료 또는 구매 완료 게시글에서 `isCommentAvailable == true`이면 댓글 icon과 댓글 수를 표시하고, `false`이면 댓글 icon과 댓글 수만 숨긴다. +- 게시글 card 본문 터치 액션은 상세 이동만 제공한다. +- 홈 커뮤니티 item에서 구매 dialog와 오디오 재생은 시작하지 않는다. +- 권한 조건을 만족하는 홈 커뮤니티 item은 작성자 영역 우측 끝의 더보기 버튼으로 관리/신고 메뉴를 시작한다. + +#### Community Tab Requirements +- 리스트형과 썸네일형 card 본문 터치 액션은 상세 이동만 제공한다. +- 리스트형 item의 오디오 재생은 제거 상태를 유지하고, 권한 조건을 만족하는 item의 작성자 영역 우측 끝에는 더보기 버튼을 제공한다. +- 썸네일형(그리드형)은 작성자 영역이 없으므로 더보기 버튼을 제공하지 않고 상세 이동만 유지한다. +- 유료 미구매 게시글 item에서는 구매 dialog를 열지 않는다. +- 리스트형과 썸네일형의 유료 미구매 가격 UI를 홈/상세와 같은 `lock icon + cash icon + 금액 capsule` 구조로 통일한다. +- 리스트형·썸네일형의 기존 reaction 숨김, 댓글 가능 여부, 고정 표시 정책은 유지한다. + +#### Detail Purchase and Badge Requirements +- 유료 미구매 게시글의 paywall을 터치한 경우에만 기존 `PurchaseCommunityPostDialog`를 표시한다. +- 상세 작성자 영역 우측 상단에 표시하던 유료 금액 badge를 제거한다. +- 유료 구매 완료 시 표시하던 구매 완료 badge를 제거한다. +- paywall 내부의 잠금/가격 표시는 구매 전 구매 진입점이므로 유지한다. +- 구매 성공 시 상세를 다시 조회하고 상위 화면에 변경 결과를 반환한다. +- 구매 실패 시 잠금 상태를 유지하고 기존 오류 Toast 정책을 따른다. + +#### Detail More Menu Requirements +- `ic_seemore_vertical` 버튼은 title bar가 아니라 게시글 작성자 영역의 우측 끝에 표시한다. +- 작성자는 무료/유료 및 구매 여부와 관계없이 더보기 버튼을 표시한다. +- 일반 사용자는 무료 게시글 또는 구매 완료 유료 게시글에서 더보기 버튼을 표시한다. +- 일반 사용자의 유료 미구매 게시글에서는 더보기 버튼을 숨긴다. +- 작성자 메뉴는 기존 `CreatorCommunityPostMenuBottomSheetDialog`를 재사용해 수정, 삭제, 고정/고정 해제를 제공한다. +- 일반 사용자 메뉴는 같은 BottomSheet의 신고 메뉴를 제공하고 기존 `CreatorCommunityReportDialog`를 재사용한다. +- 수정은 기존 `CreatorCommunityModifyActivity`, 삭제/고정은 기존 `CreatorCommunityRepository` API를 호출한다. +- 신고는 기존 `ReportType.COMMUNITY_POST` 흐름을 재사용한다. + +#### Home and Community List More Menu Requirements +- 홈 커뮤니티 item과 커뮤니티 탭 리스트형 item의 작성자 영역 우측 끝에 `ic_seemore_vertical` 버튼을 표시한다. +- 작성자 판정은 게시글 `creatorId == SharedPreferenceManager.userId`로 통일한다. +- 작성자는 무료/유료 및 구매 여부와 관계없이 더보기 버튼을 표시한다. +- 일반 사용자는 무료 게시글 또는 구매 완료 유료 게시글에서 더보기 버튼을 표시한다. +- 일반 사용자의 유료 미구매 게시글에서는 더보기 버튼을 숨긴다. +- 작성자 메뉴는 기존 `CreatorCommunityPostMenuBottomSheetDialog`를 재사용해 수정, 삭제, 고정/고정 해제를 제공한다. +- 일반 사용자 메뉴는 같은 BottomSheet의 신고 메뉴를 제공하고 기존 `CreatorCommunityReportDialog`를 재사용한다. +- 수정은 기존 `CreatorCommunityModifyActivity`, 삭제/고정은 기존 `CreatorCommunityRepository`, 신고는 기존 `ReportType.COMMUNITY_POST` 흐름을 재사용한다. +- 더보기 버튼 터치는 card의 상세 이동 click으로 전파하지 않고 메뉴만 표시한다. +- 커뮤니티 탭 썸네일형(그리드형)은 이번 후속 범위에서 제외한다. + +#### Change Propagation Requirements +- 구매·수정·삭제·고정/고정 해제 성공 시 상세는 `RESULT_OK`를 반환한다. +- 수정 복귀와 고정/고정 해제 성공 시 상세를 다시 조회한다. +- 삭제 성공 시 `RESULT_OK`를 설정하고 상세를 종료한다. +- `CreatorChannelActivity`가 상세의 `RESULT_OK`를 받으면 홈 탭과 커뮤니티 탭을 모두 새로고침한다. +- 홈·커뮤니티 리스트 메뉴의 수정·삭제·고정/고정 해제 성공도 홈 탭과 커뮤니티 탭을 모두 새로고침한다. +- 신고 결과는 게시글 구매/내용/고정 상태를 바꾸지 않으므로 홈·커뮤니티 탭 새로고침을 발생시키지 않는다. + +#### Edge Cases +- 작성자 응답의 `existOrdered == false`는 작성자의 관리 권한을 제한하지 않는다. +- 서버가 유료 미구매 홈 item에 reaction count를 내려줘도 UI에는 표시하지 않는다. +- `isCommentAvailable == false`인 무료/구매 완료 홈 item은 좋아요만 표시하고 댓글 UI는 숨긴다. +- 상세 메뉴 mutation이 실패하면 상세 상태를 임의 변경하거나 성공 결과를 반환하지 않는다. +- 홈·커뮤니티 리스트 메뉴 mutation이 실패하면 현재 목록 상태를 임의 변경하거나 성공 새로고침을 실행하지 않는다. +- 더보기 버튼이 숨겨진 유료 미구매 일반 사용자 item은 card 본문 상세 이동만 가능하다. +- 다른 화면에서 사용하는 `FeedItem.Community`는 신규 필드 기본값으로 기존 표시 동작을 유지한다. + ### F0. 2026-07-12 후속 변경사항 크리에이터 커뮤니티 상세의 오디오/구매 동작을 기존 레거시 목록 UI와 동일한 사용성으로 보정한다. @@ -107,7 +203,7 @@ Figma `290:9243`, `290:9266` 기준으로 게시글 본문과 좋아요를 표 - 본문(`content`), 이미지(`imageUrl`), 오디오(`audioUrl`)를 표시한다. - 오디오 재생은 기존 `CreatorCommunityMediaPlayerManager`를 재사용한다. - 유료 게시글(`price > 0`)이고 미구매(`existOrdered == false`)이면 Figma paywall(잠금 + 가격) 표시를 따른다. -- 유료 게시글을 구매한 경우(`existOrdered == true`) Figma 기준 `구매완료` badge를 표시한다. +- 유료 게시글 구매 완료 badge는 2026-08-10 후속 요구사항에 따라 표시하지 않는다. - 하트 터치 시 좋아요 API(`communityPostLike(postId)`)를 호출한다. - `isLiked == true`이면 `ic_feed_community_heart_fill`, `false`이면 `ic_feed_community_heart`를 표시한다. - 좋아요 수(`likeCount`)를 하트 옆에 표시한다. @@ -211,6 +307,9 @@ Figma `290:9266` 기준으로 댓글 불가 상태를 처리한다. ## 8. UX / UI Expectations - 게시글 item 터치부터 상세 진입까지 자연스럽게 이어진다. +- 홈과 커뮤니티 탭의 게시글 card 본문은 상세 이동만 수행하고, 별도 더보기 버튼은 권한별 관리/신고 메뉴만 제공한다. 구매·재생은 제공하지 않는다. +- 홈·커뮤니티 탭 리스트형·썸네일형·상세의 유료 미구매 가격은 동일한 잠금/가격 capsule 구조로 표시한다. +- 상세·홈·커뮤니티 리스트형 `ic_seemore_vertical` 버튼은 게시글 작성자 영역 우측 끝에 정렬하고 닉네임·작성 시간과 겹치지 않게 한다. - 커뮤니티 상세 상단 게시글 영역은 목록 카드처럼 rounded 배경을 깔지 않고, 게시글과 댓글 영역 및 댓글 사이에는 Figma `290:9243` 기준 1dp border를 표시한다. - 하트 상태와 좋아요 수는 터치 즉시 반응한다(낙관적 업데이트 후 실패 시 롤백). - 입력 영역은 키보드 있음/없음 상태를 기준으로 자연스럽게 대응한다. @@ -229,18 +328,30 @@ Figma `290:9266` 기준으로 댓글 불가 상태를 처리한다. - 커뮤니티 댓글/답글 신고는 현재 전용 신고 API/레거시 흐름이 없어 이번 구현 범위에서 제외하고, 기존 레거시 메뉴2의 실제 동작인 채널 크리에이터 삭제만 재사용한다. - 신규 문자열은 `values`, `values-en`, `values-ja`에 추가하거나 기존 문자열을 재사용한다. - 오디오 재생은 기존 `CreatorCommunityMediaPlayerManager`를 재사용한다. +- 홈 커뮤니티 표시는 기존 `FeedCommunityView`와 `FeedItem.Community`를 기본값 기반으로 최소 확장하며 다른 feed 호출부의 기존 동작을 유지한다. +- 상세·홈·커뮤니티 리스트형 게시글 메뉴는 기존 `CreatorCommunityPostMenuBottomSheetDialog`, `CreatorCommunityReportDialog`, `CreatorCommunityModifyActivity`, `CreatorCommunityRepository`를 호출해 사용하고 레거시 파일은 수정하지 않는다. +- 홈·커뮤니티 리스트형 메뉴 실행과 mutation은 `CreatorChannelActivity`의 공용 처리 흐름을 사용하고 화면별 중복 mutation 구현이나 신규 범용 coordinator를 추가하지 않는다. +- UI 자동 테스트는 Response/mapper/presentation/routing/result 전파 로직만 검증하고 View 크기·간격·constraint·visibility 속성을 직접 검증하지 않는다. --- ## 10. Metrics - 별도 분석 이벤트 추가는 이번 범위에서 정의하지 않는다. - 기능 성공 기준은 게시글 상세 진입, 댓글/답글 조회, 좋아요/댓글/답글 작성·수정·삭제가 정상 동작하고 목록에 반영되는 것이다. +- 2026-08-10 후속 성공 기준은 홈 Response 신규 필드가 고정/댓글 UI에 반영되고, 미구매 유료 홈 item reaction이 숨겨지며, card 본문은 상세 이동만 수행하고 권한별 더보기 메뉴가 상세·홈·커뮤니티 리스트형에 표시되며, 구매·메뉴 결과가 홈과 커뮤니티 탭에 반영되는 것이다. - UI 완료 기준은 Figma `290:9243`, `290:9266`, `669:40462`, `669:40468`의 주요 layout 구조, 하트/전송 버튼 상태, 댓글 불가 처리, 입력 영역 동작이 구현 화면에 반영되었는지 확인되는 것이다. -- 자동 검증 기준은 조회 API 계약(page/size 기본값, 응답 매핑), 하트 icon/전송 버튼 상태 계약, 댓글 불가 게시글의 댓글/입력 영역 숨김 계약을 source/resource/ViewModel 테스트로 확인하는 것이다. +- 자동 검증 기준은 조회 API 계약(page/size 기본값, 응답 매핑), 하트 icon/전송 버튼 상태 계약, 댓글 불가 게시글의 댓글/입력 영역 숨김 계약과 홈 신규 Response 필드, 잠금·댓글·핀 표시 조건, 상세·홈·커뮤니티 리스트형 더보기 권한, 결과 전파를 local unit test로 확인하는 것이다. --- ## 11. Open Questions & Decisions +- 결정(2026-08-10, 목록 더보기 후속): 상세와 동일한 권한별 더보기 메뉴를 홈 커뮤니티 item과 커뮤니티 탭 리스트형에 적용한다. 작성자 영역이 없는 커뮤니티 그리드형은 제외한다. +- 결정(2026-08-10, 목록 click): card 본문은 상세 이동, 더보기 버튼은 메뉴 표시로 분리하며 더보기 touch는 상세 이동으로 전파하지 않는다. +- 결정(2026-08-10, 구현 구조): 홈·커뮤니티 리스트형은 `CreatorChannelActivity`의 공용 메뉴/mutation 흐름을 사용하고 기존 상세 메뉴 구현은 유지한다. +- 결정(2026-08-10, 더보기 권한): 작성자는 구매 여부와 관계없이 관리 메뉴를 표시하고, 일반 사용자는 무료 또는 구매 완료 게시글에서만 신고 메뉴를 표시한다. +- 결정(2026-08-10, 가격 UI): 커뮤니티 탭 리스트형과 썸네일형 모두 홈/상세의 잠금/가격 capsule 표현을 따른다. +- 결정(2026-08-10, 홈 고정 표시): 홈 Response의 `isPinned`를 홈 커뮤니티 item 고정 표시에 사용한다. +- 결정(2026-08-10, 문서 연속성): 상세 구매 후속 요구사항을 포함하는 기존 문서에 이번 홈/탭/상세 정리 범위를 이어서 기록한다. - 결정(재사용 vs 복사): 답글 목록 화면 UI는 팬Talk 상세와 동일하지만 조회 API가 다르고, 현재 팬Talk 상세 화면은 v2에 미구현이다. 따라서 이번 범위에서는 **커뮤니티 전용 답글 화면으로 신규 구현하되, layout XML과 댓글/답글 item view(ViewHolder)만 데이터/콜백 주입형으로 설계해 재사용 가능하게 둔다.** ViewModel/Repository/데이터 모델/화면 컨트롤러는 커뮤니티 전용으로 분리한다. - 근거: 아직 팬Talk 상세라는 두 번째 사용처가 없어 지금 데이터 계층까지 공통 추상화하면 단일 사용처를 위한 추측성 추상화가 된다. UI 리소스/item view만 공용화하면 추후 팬Talk 상세 구현 시 동일 UI를 그대로 재사용할 수 있다. - 남은 확인: 없음. diff --git a/docs/20260708_커뮤니티_게시물_상세_댓글_답글/reviews/phase15-community-purchase-detail-menu-review.md b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/reviews/phase15-community-purchase-detail-menu-review.md new file mode 100644 index 00000000..bb261116 --- /dev/null +++ b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/reviews/phase15-community-purchase-detail-menu-review.md @@ -0,0 +1,242 @@ +# Phase 15 커뮤니티 구매·상세 메뉴 리뷰 보고서 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 15 / `P15-T1`~`P15-T5`, `P15-GATE` | +| 기준 commit 또는 working tree | `02cd583f` 기반 미커밋 working tree | +| 리뷰 일자 | 2026-08-10 | +| 리뷰어 | Codex, 독립 재검토 `phase15_review` | +| 기준 문서 | `prd.md` F0-20260810, `plan-task.md` Phase 15 | +| 리뷰 상태 | 수정 검증 완료 | + +## 2. 리뷰 목적과 범위 + +### 목적 + +- Phase 15의 미체크 항목이 단순 체크 누락인지 미구현인지 확인한다. +- PRD의 홈 Response, 유료 미구매 표시, 상세 메뉴 권한, 상위 새로고침 요구사항을 코드·테스트·Gate와 대조한다. + +### 포함 범위 + +- 코드: Phase 15 파일 구조에 나열된 홈, 커뮤니티 탭, 상세, 상위 Activity, action 파일 +- 테스트: Phase 15 focused local unit/source contract 테스트 +- 문서: `prd.md` F0-20260810, `plan-task.md` Phase 15 +- 수동 검증: 코드/diff 정적 검토 + +### 제외 범위 + +- `app/src/androidTest`, 기기·에뮬레이터 UI 조작, 스크린샷·시각 QA +- 서버 Response 실제 배포 여부와 외부 API 운영 상태 + +## 3. 판정 기준 + +| 심각도 | 기준 | +|---|---| +| Blocker | 보안·데이터 손실, 핵심 흐름 불능 | +| High | 확정 PRD/API 계약 위반 또는 주요 회귀 | +| Medium | 제한 조건의 기능·접근성·복구 문제 | +| Low | 유지보수성, 문서 정합성, style Gate 문제 | + +## 4. 검토한 근거 + +### 문서와 코드 + +- 요구사항: PRD `Home Response Contract`, `Home Community Requirements`, `Community Tab Requirements`, `Detail Purchase and Badge Requirements`, `Detail More Menu Requirements`, `Change Propagation Requirements` +- 계획: `P15-T1`~`P15-T5`, `P15-GATE` +- 코드/테스트: Phase 15 파일 구조와 각 Task에 명시된 파일 + +### 실행 환경 + +```text +OS: Darwin 25.0.0 x86_64 +Java: OpenJDK 17.0.15 +Android/Gradle: 저장소 wrapper 및 debug variant +민감 환경 값: 기록하지 않음 +``` + +### 실행한 검증 + +| 명령 | 결과 | 핵심 증거 | +|---|---|---| +| 홈 focused unit test | 성공 | `BUILD SUCCESSFUL` | +| 커뮤니/action focused unit test | 성공 | `BUILD SUCCESSFUL` | +| `CreatorChannelActivitySourceTest` | 성공 | `BUILD SUCCESSFUL` | +| `./gradlew :app:mergeDebugResources` | 성공 | `BUILD SUCCESSFUL` | +| `./gradlew :app:compileDebugKotlin` | 성공 | `BUILD SUCCESSFUL` | +| `./gradlew :app:ktlintCheck` | 실패 | `CreatorChannelActivity.kt:29:1 Unused import` 1건 | +| `git diff --check` | 성공 | 출력 없음 | +| 수정 후 전체 focused test | 성공 | `BUILD SUCCESSFUL` | +| 수정 후 resource/compile/ktlint Gate | 성공 | `BUILD SUCCESSFUL` | +| 수정 후 `git diff --check`, `git diff HEAD --check` | 성공 | 출력 없음 | + +## 5. 발견 사항 요약 + +| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | +|---|---|---|---|---|---| +| `REV-P15-001` | Low | 수정 완료 | 목록 메뉴 제거 후 미사용 import가 style Gate를 막음 | `P15-R1` | 미사용 import 1줄 제거 및 Gate 재검증 | +| `REV-P15-002` | High | 수정 완료 | 홈 reaction id가 작성자 row에 연결됨 | `P15-R2` | reaction id를 실제 하트·댓글 row로 이동 및 Gate 재검증 | +| `REV-P15-003` | High | 수정 완료 | 공유 FeedItem 잠금 정책이 다른 홈 화면 reaction을 숨김 | `P15-R3` | 크리에이터 채널 홈만 잠금 숨김 정책을 선택하고 공유 기본값 회귀 검증 | +| `REV-P15-004` | Low | 수정 완료 | 메인 홈 회귀 테스트 파일의 미사용 import가 style Gate를 막음 | `P15-R4` | 미사용 import 1줄 제거 및 Gate 재검증 | + +## 6. 발견 사항 상세 + +### REV-P15-001 — 목록 메뉴 제거 후 미사용 import가 style Gate를 막음 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** `P15-T5` orphan 제거, `P15-GATE` +- **소유 Task:** `P15-R1` + +**관찰 내용** + +`CreatorChannelActivity`의 목록 고정/삭제 로직을 제거한 후 `SharedPreferenceManager` import가 남아 `ktlintCheck`가 실패했다. + +**근거** + +- 코드: `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel/CreatorChannelActivity.kt:29` +- 검증: `./gradlew :app:ktlintCheck` 결과 `Unused import (no-unused-imports)` +- 문서: `plan-task.md` `P15-T5`, `P15-GATE` + +**영향** + +기능 동작에는 영향이 없으나 Phase 15 style Gate를 통과할 수 없다. + +**권장 조치** + +`SharedPreferenceManager` import 1줄만 제거하고 `ktlintCheck`와 Phase 15 Gate를 재실행한다. + +**판정 기록** + +- 2026-08-10 — 기능 테스트·resource merge·compile은 통과했고 ktlint 정확한 1건으로 확정했다. +- 2026-08-10 — `SharedPreferenceManager` import 1줄 제거 후 최종 `ktlintCheck`와 Phase 15 Gate가 PASS했다. + +### REV-P15-002 — 홈 reaction id가 작성자 row에 연결됨 + +- **심각도:** High +- **상태:** 수정 완료 +- **관련 요구사항:** PRD `Home Community Requirements`, `P15-T2` +- **소유 Task:** `P15-R2` + +**관찰 내용** + +`FeedCommunityView.bind()`는 `ll_feed_community_reaction`에 `item.showReaction`을 적용하지만, XML의 해당 id가 프로필·닉네임·작성 시간 row에 붙어 있다. 실제 하트·댓글 row에는 id가 없다. + +**근거** + +- XML: `view_feed_community.xml:36-41`의 `ll_feed_community_reaction` 아래에 `iv_feed_community_profile`이 있음 +- XML: `view_feed_community.xml:154-195`의 실제 reaction row에 id가 없음 +- 코드: `FeedCommunityView.kt:51`, `FeedCommunityView.kt:81` + +**재현 또는 검증 절차** + +1. `price > 0 && existOrdered == false`인 홈 item의 `showReaction == false`를 확인한다. +2. `FeedCommunityView.bind()`가 `ll_feed_community_reaction.isVisible = false`를 적용한다. +3. XML tree에서 해당 id가 작성자 row를 가리키므로 작성자 row가 숨겨진다. +4. 실제 reaction row는 자신의 visibility binding을 받지 않아 하트·댓글 UI가 남는다. + +**영향** + +유료 미구매 홈 item이 PRD와 반대로 작성자 영역을 숨기고 reaction UI를 노출한다. + +**권장 조치** + +`ll_feed_community_reaction` id를 작성자 row에서 실제 하트·댓글 row로 이동한다. + +**판정 기록** + +- 2026-08-10 — XML tree와 binding 대상을 대조해 PRD 2번을 재현하는 실제 결함으로 확정했다. +- 2026-08-10 — id를 실제 하트·댓글 reaction row로 이동하고 focused/resource/compile 및 최종 Gate PASS를 확인했다. + +### REV-P15-003 — 공유 FeedItem 잠금 정책이 다른 홈 화면 reaction을 숨김 + +- **심각도:** High +- **상태:** 수정 완료 +- **관련 요구사항:** PRD `Edge Cases`, `Constraints & Dependencies`, `P15-T1`~`P15-T2` +- **소유 Task:** `P15-R3` + +**관찰 내용** + +`FeedItem.Community.showReaction = !isLocked`와 공유 `FeedCommunityView.bind()` 조합이 크리에이터 채널 홈뿐 아니라 메인 홈 인기 커뮤니티에도 적용된다. 메인 홈 mapper도 `price`, `existOrdered`를 전달하므로 유료 미구매 item의 기존 reaction 표시가 함께 사라진다. + +**근거** + +- 공유 정책: `FeedItem.kt`의 `showReaction`, `showComment` +- 다른 호출부: `HomeRecommendationMappers.kt:84-103`이 `price`, `existOrdered`를 전달 +- 공유 View 사용: `HomePopularCommunityAdapter.kt:49`의 `view.bind(item.item)` +- 문서: PRD `다른 화면에서 사용하는 FeedItem.Community는 신규 필드 기본값으로 기존 표시 동작을 유지한다.` + +**영향** + +요구 범위 밖인 메인 홈 인기 커뮤니티의 유료 미구매 item 표시가 변경되어 기존 화면 회귀가 발생한다. + +**권장 조치** + +공유 모델의 신규 정책 기본값은 기존 reaction 표시를 유지하고, 크리에이터 채널 홈 mapper만 잠금 reaction 숨김을 명시적으로 선택한다. + +**판정 기록** + +- 2026-08-10 — 전체 `FeedItem.Community` production 생성자와 공유 View 호출부를 추적해 실제 범위 확장 결함으로 확정했다. +- 2026-08-10 — 기본값은 기존 reaction 표시를 유지하고 크리에이터 채널 홈만 opt-in하도록 수정했다. 공유 모델·메인 홈 mapper 회귀 테스트와 최종 Gate가 PASS했다. + +### REV-P15-004 — 메인 홈 회귀 테스트 파일의 미사용 import가 style Gate를 막음 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** `P15-R3`, `P15-GATE` +- **소유 Task:** `P15-R4` + +**관찰 내용** + +메인 홈 mapper 회귀 테스트 추가로 `HomeMainFragmentLayoutTest`가 다시 lint 대상이 되면서 사용처가 없는 `ColorDrawable` import 1건이 확인됐다. + +**근거** + +- 코드: `HomeMainFragmentLayoutTest.kt:6` +- 검증: `./gradlew :app:ktlintCheck` 결과 `Unused import` + +**영향** + +기능 동작에는 영향이 없으나 Phase 15 style Gate가 실패한다. + +**권장 조치** + +사용처가 없는 `ColorDrawable` import 1줄만 제거한다. + +**판정 기록** + +- 2026-08-10 — `rg`로 import 외 사용처가 없음을 확인해 정확한 style Gate 결함으로 확정했다. +- 2026-08-10 — `ColorDrawable` import 1줄 제거 후 `ktlintCheck`와 최종 focused test Gate가 PASS했다. + +## 7. 확정 항목의 plan·goal 전환 + +- `plan-task.md` Phase 15에 `Task 15.R1` / goal `P15-R1`, `Task 15.R2` / goal `P15-R2`, `Task 15.R3` / goal `P15-R3`, `Task 15.R4` / goal `P15-R4`를 추가했다. +- 각 항목의 대상 파일, 최소 수정 범위, 회귀 검증, 완료 증거를 명시했다. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | PRD/plan·production/test/diff 대조 | +| 후보 항목 판정 완료 | 충족 | `REV-P15-001`~`REV-P15-004` 확정 | +| 확정 항목 plan 반영 | 충족 | `P15-R1`~`P15-R4` | +| 보류 항목 기록 | 해당 없음 | 없음 | +| 검증 명령과 결과 기록 | 충족 | `P15-R1`~`P15-R4` 수정 후 최종 Gate PASS | + +**최종 결론:** 수정 검증 완료 + +**남은 항목:** 없음. 기기·에뮬레이터 수동 UI 확인과 실제 서버 Response 배포 상태는 리뷰 범위 밖이다. + +## 9. 수정 후 검증 기록 + +- `REV-P15-001`: `CreatorChannelActivity`의 미사용 `SharedPreferenceManager` import 1줄을 제거했다. +- `REV-P15-002`: `ll_feed_community_reaction` id를 프로필 row에서 실제 하트·댓글 row로 이동했다. +- `REV-P15-003`: `FeedItem.Community.hideReactionWhenLocked` 기본값을 `false`로 두고 크리에이터 채널 홈 mapper만 `true`를 전달했다. 메인 홈 인기 커뮤니티 잠금 item의 기존 reaction 표시 회귀 테스트를 추가했다. +- `REV-P15-004`: `HomeMainFragmentLayoutTest`의 미사용 `ColorDrawable` import 1줄을 제거했다. +- 최종 전체 focused test: `BUILD SUCCESSFUL`. +- 최종 `:app:mergeDebugResources`, `:app:compileDebugKotlin`, `:app:ktlintCheck`: `BUILD SUCCESSFUL`. +- 최종 `git diff --check`, `git diff HEAD --check`: 출력 없이 PASS. +- 정적 재검토: 구매 호출은 상세에만 존재하고, 홈·커뮤니티 item은 상세 이동만 수행하며, 상세 배지 제거·권한별 메뉴·홈+커뮤니티 refresh 계약이 유지됨을 확인했다. +- 독립 재검토: Critical/Minor 없음, Important 1건(`REV-P15-003`)을 수정 반영했다. +- 수정 후 독립 최종 재검토: `REV-P15-001`~`REV-P15-004` 반영을 확인했고 새 Critical/Important/Minor 이슈 없음으로 판정했다. diff --git a/docs/20260708_커뮤니티_게시물_상세_댓글_답글/reviews/phase16-home-community-list-more-menu-review.md b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/reviews/phase16-home-community-list-more-menu-review.md new file mode 100644 index 00000000..f98694d6 --- /dev/null +++ b/docs/20260708_커뮤니티_게시물_상세_댓글_답글/reviews/phase16-home-community-list-more-menu-review.md @@ -0,0 +1,130 @@ +# Phase 16 홈·커뮤니티 리스트 더보기 메뉴 리뷰 보고서 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 16 / `P16-T1`~`P16-T3`, `P16-GATE` | +| 기준 commit 또는 working tree | 현재 `main` 미커밋 working tree | +| 리뷰 일자 | 2026-08-10 | +| 리뷰어 | Sisyphus, 독립 목표·QA·품질·보안·맥락 검토 | +| 기준 문서 | `prd.md` F0-20260810, `plan-task.md` Phase 16 | +| 리뷰 상태 | 수정 검증 완료 | + +## 2. 리뷰 목적과 범위 + +### 목적 + +- 홈과 커뮤니티 리스트형의 권한별 더보기 메뉴가 PRD와 일치하는지 확인한다. +- 완료 체크박스와 production/test/Gate/문서 기록이 일치하는지 확인한다. + +### 포함 범위 + +- 코드: Phase 16 공용 권한 모델, 홈·리스트 callback, Activity 메뉴·mutation +- 테스트: Phase 16 focused local unit/source contract 테스트 +- 문서: `prd.md` F0-20260810, `plan-task.md` Phase 16 +- 수동 검증: 코드·XML·diff 정적 검토 + +### 제외 범위 + +- `app/src/androidTest`, 기기·에뮬레이터 조작, 스크린샷·시각 QA +- 실제 서버 Response와 API mutation, Git commit, 별도 worktree + +## 3. 판정 기준 + +| 심각도 | 기준 | +|---|---| +| Blocker | 보안·데이터 손실, 핵심 흐름 불능 | +| High | 확정 PRD/API 계약 위반 또는 주요 회귀 | +| Medium | 제한 조건의 기능·접근성·복구 문제 | +| Low | 유지보수성, 문서 정합성, style Gate 문제 | + +## 4. 검토한 근거 + +- 요구사항: PRD `Home and Community List More Menu Requirements`, `Change Propagation Requirements` +- 계획: `P16-T1`~`P16-T3`, `P16-GATE` +- 코드/테스트: Phase 16 Task에 명시된 파일과 focused test +- 실행 환경: macOS, 저장소 Gradle wrapper, debug variant + +| 명령 또는 검증 | 결과 | 핵심 증거 | +|---|---|---| +| 홈·Feed·Activity focused test | 성공 | `BUILD SUCCESSFUL` | +| community/action focused test | 성공 | `BUILD SUCCESSFUL` | +| `./gradlew :app:mergeDebugResources` | 성공 | `BUILD SUCCESSFUL` | +| `./gradlew :app:compileDebugKotlin` | 성공 | `BUILD SUCCESSFUL` | +| `./gradlew :app:ktlintCheck` | 성공 | 기존 `disabled_rules` 경고만 존재 | +| `git diff --check`, `git diff HEAD --check` | 성공 | 출력 없음 | + +## 5. 발견 사항 요약 + +| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | +|---|---|---|---|---|---| +| `REV-P16-001` | Low | 수정 완료 | Phase 16 구현·Gate 완료 상태가 문서에 누락됨 | `P16-R1` | 완료 기록과 Verification Log 누적 | + +## 6. 발견 사항 상세 + +### REV-P16-001 — Phase 16 구현·Gate 완료 상태가 문서에 누락됨 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** Phase 16 완료 조건, `P16-GATE` +- **소유 Task:** `P16-R1` + +**관찰 내용** + +모든 Phase 16 Task와 Gate가 통과했지만 계획 기록은 `구현 미시작`으로 남고 `Verification Log`에 Gate 결과가 없었다. + +**근거** + +- 문서: `plan-task.md` Phase 16 계획 기록과 `Verification Log` +- 검증: 최종 5개 리뷰 중 목표·코드 품질·맥락 검토가 동일 문서 불일치를 차단 이슈로 판정 + +**영향** + +기능 동작에는 영향이 없지만 문서상 완료 조건을 충족하지 못해 Phase 완료 판정을 내릴 수 없다. + +**권장 조치** + +기존 계획 기록은 보존하고 구현·Gate 완료 기록과 `Verification Log`를 누적한 뒤 최종 리뷰를 재실행한다. + +**판정 기록** + +- 2026-08-10 — 코드와 Gate는 통과했으나 완료 문서가 실제 상태와 달라 확정했다. +- 2026-08-10 — 기존 계획 기록을 보존하고 Phase 16 구현·Gate 완료 기록과 `Verification Log`를 누적했다. + +## 7. 확정 항목의 plan·goal 전환 + +- `plan-task.md`에 `Task 16.R1` / goal `P16-R1`을 추가했다. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | PRD/plan·production/test/diff 대조 | +| 후보 항목 판정 완료 | 충족 | `REV-P16-001` 확정 | +| 확정 항목 plan 반영 | 충족 | `P16-R1` | +| 보류 항목 기록 | 해당 없음 | 없음 | +| 검증 명령과 결과 기록 | 충족 | Phase 16 구현 기록과 `Verification Log` 누적 | + +**최종 결론:** 수정 검증 완료 + +**남은 항목:** 없음. 실제 서버 권한 검증과 기기 UI 확인은 리뷰 범위 밖이다. + +## 9. 수정 후 검증 기록 + +- 수정 전: Phase 16 계획 기록은 구현 미시작이며 `Verification Log`에 Gate 결과가 없었다. +- 1차 수정: 기존 계획 기록 뒤에 구현·Gate 완료 기록을 추가하고 `Verification Log`에 최종 자동 검증과 제외 범위를 누적했다. +- 최종 검증: `git diff --check`, `git diff HEAD --check`가 출력 없이 PASS했다. +- 목표·제약 검토: PASS, 차단 이슈 없음. +- 실행 QA: PASS, 허용된 P0 시나리오 11/11과 focused 128개, community/action 96개 PASS. +- 코드 품질 검토: PASS, 차단 이슈 없음. +- 보안 검토: PASS, 클라이언트 범위의 Critical/High 이슈 없음. 서버 소유권·rate limit 검증은 저장소 밖이라 확인하지 못했다. +- 맥락 검토: PASS, 추가 요구사항·호출자·Git 이력 충돌 없음. + +### 2차 독립 재점검 — 2026-08-11 + +- 문서 체크: `plan-task.md`의 실제 Task 체크박스는 모두 완료 상태이며, 체크 누락 또는 미구현 항목이 없음을 production/test와 역대조했다. +- 요구사항 대조: 더보기 권한 truth table, 홈·리스트 root/more click 분리, grid 제외, 작성자 관리/일반 사용자 신고, 성공 관리 작업의 홈+커뮤니티 갱신, 신고·실패 no-refresh가 PRD와 일치했다. +- 공유 회귀 대조: `FeedItem.Community.hideReactionWhenLocked=false`, `showMore=false` 기본값과 크리에이터 채널 홈의 명시적 opt-in을 확인해 다른 공유 feed의 기존 표시를 보존함을 확인했다. +- 자동 검증: Phase 15·16 focused test, community/action focused test, 전체 `:app:testDebugUnitTest`, resource merge, Kotlin compile, ktlint, 두 diff check가 모두 PASS했다. +- 독립 리뷰: Critical/Important/Minor 이슈 없음, 승인 가능 판정. 새 확정 항목이 없어 회귀 수정 Task와 production/test 수정은 추가하지 않았다.