# 메인 홈 추천 UI와 API 연동 Plan / Task > **For agentic workers:** 구현 시 `superpowers:subagent-driven-development` 또는 `superpowers:executing-plans`를 사용해 task 단위로 진행한다. 각 단계는 체크박스(`- [ ]`)로 추적하고, 완료 즉시 `- [x]`로 갱신한다. **Goal:** Figma `home_001`(`24:5514`) 기준 메인 홈 추천 화면을 `HomeMainFragment`에 구성하고, `GET /api/v2/home/recommendations` 및 모두 팔로우 API를 기존 XML View + v2 widget 패턴으로 연동한다. **Architecture:** 신규 홈 추천 API, Repository, ViewModel, DTO/UI model/mapper는 `kr.co.vividnext.sodalive.v2.main.home` 하위에 둔다. 화면은 `HomeMainFragment`와 `fragment_v2_main_home.xml`을 확장하고, 기존 `v2.widget` 컴포넌트는 재사용하되 추천 화면에 필요한 최소 확장만 수행한다. **Tech Stack:** Kotlin, Android XML Views, ViewBinding, RecyclerView, RxJava3, Retrofit, Gson, Koin, Coil, local unit test. --- ## 전제와 성공 기준 - PRD: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md` - 기존 `HomeApi`에는 메서드를 추가하지 않고 신규 `HomeRecommendationApi`를 만든다. - 신규 화면 관련 하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다. - `추천 필모그래피`, `또 다른 모습` 섹션은 만들지 않는다. - 각 API 리스트가 비어 있으면 해당 section-title과 목록을 숨긴다. - 구현 완료 후 최소 `./gradlew :app:testDebugUnitTest`와 `./gradlew :app:ktlintCheck`를 실행한다. --- ### Phase 1: 기존 구조 확인과 구현 경계 고정 - [x] **Task 1.1: 기존 홈/위젯/네트워크 패턴 확인** - 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 확인: `app/src/main/res/layout/fragment_v2_main_home.xml` - 확인: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt` - 확인: `app/src/main/java/kr/co/vividnext/sodalive/home/HomeApi.kt` - 확인: `app/src/main/java/kr/co/vividnext/sodalive/common/ApiResponse.kt` - 확인: `app/src/main/java/kr/co/vividnext/sodalive/extensions/ImageExtensions.kt` - 검증: `HomeMainFragment`는 현재 빈 `FrameLayout` 바인딩만 가지고 있고, 신규 API 등록은 `AppDI.kt`의 `networkModule`, `repositoryModule`, `viewModelModule`에 추가해야 함을 확인한다. - [x] **Task 1.2: 재사용 위젯과 신규 UI 범위 확정** - 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerView.kt` - 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/livethumbnail/LiveThumbnailSimpleView.kt` - 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt` - 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/characterchatthumbnail/CharacterChatThumbnailView.kt` - 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt` - 확인: `app/src/main/res/layout/view_section_title.xml` - 신규 필요 후보: 최근 활동 카드, 최근 데뷔 크리에이터 카드, 장르별/응원 크리에이터 profile grid, 모두 팔로우 버튼, 사업자 정보 접기 영역. - 검증: PRD의 Section Mapping 표와 구현 대상/제외 대상이 일치하는지 문서 체크한다. #### Task R1.1: HomeMainFragment 현재 경로 추적성 복구 **Goal 실행 `P1-R1`:** 현재 `HomeMainFragment` 경로와 PRD·계획의 활성 경로 안내를 일치시켜 후속 작업이 존재하지 않는 과거 경로를 기준으로 삼지 않게 한다. - **시작 조건:** `REV-P1-001` 확정 및 `reviews/phase1-existing-boundary-review.md` 판정 완료. - **완료 증거:** 현재 파일 존재/과거 파일 미존재 재현, PRD 활성 제약과 Phase 1 현재 경로 안내 정정, 문서 검색과 `git diff --check` 성공. - **범위 밖:** 완료된 과거 Task의 파일 이력 삭제, production 패키지 이동, 관련 없는 홈 구조 리팩터링. - **TDD 예외 사유:** production 동작을 바꾸지 않는 문서 경로 추적성 정정이므로 실패 test 대신 파일 존재와 문서 검색을 대체 검증으로 사용한다. **Files:** - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase1-existing-boundary-review.md` - [x] `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` 존재와 과거 `v2/main/HomeMainFragment.kt` 미존재를 재현한다. - [x] PRD Technical Constraints와 Phase 1에 현재 canonical 경로를 명시하되 기존 완료 이력은 삭제하지 않는다. - [x] `rg` 경로 검색과 `git diff --check`를 실행하고 결과를 Task 검증 기록에 누적한다. - 검증 기록: 2026-07-31 `test -f "app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt"`와 `test ! -f "app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt"`를 실행해 현재 파일 존재와 과거 파일 미존재를 확인했다. PRD Technical Constraints에는 현재 canonical 경로인 `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`와 package `kr.co.vividnext.sodalive.v2.main.home.HomeMainFragment`를 명시하고 과거 경로 미존재를 덧붙였다. - 검증 기록: 2026-07-31 `rg -n "v2\.main\.HomeMainFragment|v2/main/HomeMainFragment|v2/main/home/HomeMainFragment|kr\.co\.vividnext\.sodalive\.v2\.main\.home\.HomeMainFragment|kr\.co\.vividnext\.sodalive\.v2\.main\.HomeMainFragment"`로 PRD, 계획, Phase 1 리뷰의 현재/과거 경로 참조를 확인했다. `git diff --check`는 출력 없이 성공했다. --- ### Phase 2: API DTO, Repository, DI 추가 - [x] **Task 2.1: 홈 추천 API/DTO 파일 생성** - 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationApi.kt` - 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt` - 구현 내용: - `GET /api/v2/home/recommendations` - `POST /api/v2/home/recommendations/creators/follow` - `FollowRecommendedCreatorsRequest(val creatorIds: List)` - `HomeRecommendationResponse`와 PRD의 모든 item DTO - 주의: 서버 필드명은 임의 변경하지 않고 필요할 때만 `@SerializedName`을 추가한다. - 검증: `HomeRecommendationResponse` 필드가 현재 계약 기준 `lives`, `banners`, `recentlyActiveCreators`, `recentDebutCreators`, `aiCharacters`, `genreCreators`, `cheerCreators`, `popularCommunityPosts`를 포함하고 `firstAudioContents`는 포함하지 않는다. - [x] **Task 2.2: Repository 생성** - 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationRepository.kt` - 구현 내용: - `getRecommendations(token: String)` - `followRecommendedCreators(request: FollowRecommendedCreatorsRequest, token: String)` - 검증: 기존 Repository처럼 Retrofit `Single>`를 그대로 반환하고, token은 호출부에서 `"Bearer ${SharedPreferenceManager.token}"` 형태로 전달한다. - [x] **Task 2.3: Koin DI 등록** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt` - 구현 내용: - `networkModule`에 `HomeRecommendationApi` 등록 - `repositoryModule`에 `HomeRecommendationRepository` 등록 - 이후 Phase 4에서 생성할 `HomeRecommendationViewModel` 등록 - 검증 명령: `./gradlew :app:compileDebugKotlin` - 기대 결과: 신규 API/Repository import 및 Koin 등록 컴파일 성공. --- ### Phase 3: 순수 mapper와 unit test 작성 - [x] **Task 3.1: activity type 표시 mapper 테스트 작성** - 생성: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/RecommendedActivityTypeTest.kt` - 대상: `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY`, 알 수 없는 code. - 기대: - `LIVE`, `LIVE_REPLAY` -> `R.string.home_recommendation_activity_live` - `AUDIO` -> `R.string.home_recommendation_activity_audio` - `COMMUNITY` -> `R.string.home_recommendation_activity_community` - unknown -> null 또는 미표시 정책 - 검증 명령: `./gradlew :app:testDebugUnitTest --tests kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest` - 기대 결과: mapper 구현 전 실패. - [x] **Task 3.2: activity type mapper 구현** - 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/RecommendedActivityType.kt` - 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt` - 구현 내용: - 백엔드 code 문자열을 앱 enum으로 변환 - 표시 문자열은 `@StringRes`로 반환해 Fragment/ViewHolder에서 `getString()` 처리 - 알 수 없는 code는 크래시 없이 null 반환 후 UI에서 activity label 미표시 - 검증 명령: `./gradlew :app:testDebugUnitTest --tests kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest` - 기대 결과: PASS. - [x] **Task 3.3: string resource 추가** - 수정: `app/src/main/res/values/strings.xml` - 수정: `app/src/main/res/values-en/strings.xml` - 수정: `app/src/main/res/values-ja/strings.xml` - 추가 문자열: - 추천/랭킹/팔로잉 tab - 섹션 제목 - `라이브`, `오디오`, `커뮤니티` - `모두 팔로우 하기`, `모두 팔로우 완료` - `더보기`, `접기` - 사업자 정보 텍스트 - 검증 명령: `./gradlew :app:mergeDebugResources` - 기대 결과: resource merge 성공. --- ### Phase 4: ViewModel과 화면 상태 구성 - [x] **Task 4.1: UI model 정의** - 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiState.kt` - 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt` - 구현 내용: - `Loading`, `Content`, `Empty`, `Error` 상태 - 섹션별 UI model 또는 adapter item - `HomeAiCharacterItem.originalWorkTitle == null`이면 기존 `CharacterChatThumbnailView`의 `shouldShowOriginalTitle = false` - `HomePopularCommunityPostItem`의 `price`, `existOrdered` 기반 유료 상태 모델 - 검증: DTO를 Fragment/ViewHolder에 직접 노출하지 않는다. - [x] **Task 4.2: ViewModel 생성** - 생성: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeRecommendationViewModel.kt` - 구현 내용: - `recommendationStateLiveData` - `toastLiveData` - `isLoading` - `loadRecommendations()` - `followCreators(sectionKey: String, creatorIds: List)` - 빈 `creatorIds`면 API 미호출 - 모두 팔로우 success 후 해당 section/button 상태 완료 처리 - 검증: `ApiResponse.success == false`, `data == null`, Throwable 상황에서 기존 패턴처럼 unknown error toast를 노출한다. - [x] **Task 4.3: ViewModel DI 등록 완료** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt` - 구현 내용: `viewModel { HomeRecommendationViewModel(get()) }` - 검증 명령: `./gradlew :app:compileDebugKotlin` - 기대 결과: Koin 등록 및 ViewModel 생성 컴파일 성공. --- ### Phase 5: HomeMainFragment 레이아웃과 상단 UI 구현 - [x] **Task 5.1: 홈 추천 화면 레이아웃 작성** - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 포함: - `include` 또는 직접 배치로 `view_title_bar_home` - `TextTabBarView` - `TextTabBarView` 아래 추천 content 전용 세로 `NestedScrollView` 또는 동등한 scroll container - 추천 content 내부 섹션별 컨테이너와 가로 `RecyclerView` - 추천 content 내부 사업자 정보 영역 - 제외: - `ViewPager2` - `FragmentStateAdapter` - `랭킹`, `팔로잉`용 별도 Fragment - `랭킹`, `팔로잉`용 숨김 View를 미리 배치하는 `VISIBLE`/`GONE` 구조 - 검증: root background는 기존처럼 `@color/black` 유지하고, 세로 스크롤 시 title-bar와 `TextTabBarView`는 화면에 남으며 그 아래 추천 content만 스크롤된다. - [x] **Task 5.2: title bar와 tab bar 바인딩** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 확인: `app/src/main/res/layout/view_title_bar_home.xml` - 확인: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/TextTabBarView.kt` - 구현 내용: - right icons 순서: `ic_bar_cash`, `ic_bar_search`, `ic_bar_bell` - tabs 순서: 추천, 랭킹, 팔로잉 - 추천 tab selected 상태 - tab 전환은 글자 터치 callback으로만 처리 - 좌우 swipe 전환은 추가하지 않음 - 이번 범위에서는 추천 tab content만 바인딩하고 `랭킹`, `팔로잉` content는 생성하지 않음 - 검증: 앱 실행 시 상단 bar와 tab이 표시되고 text resource가 적용된다. 좌우 swipe로 tab 전환이 발생하지 않는다. --- ### Phase 6: 섹션 adapter와 재사용 위젯 바인딩 > Phase 6까지는 실제 API/ViewModel 연동 전 단계이므로, 실제 기기에서 UI 배치와 섹션 표시 형태를 확인할 수 있도록 Fragment 또는 adapter 호출부에 샘플 데이터를 임시 주입할 수 있다. 샘플 데이터는 Phase 9의 실제 `HomeRecommendationViewModel` observe/API 바인딩 전 제거하거나 실제 상태 바인딩으로 대체한다. - [x] **Task 6.1: 라이브, 배너 섹션 바인딩** - 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt` - 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeBannerBinder.kt` - 재사용: `LiveThumbnailSimpleView`, `BannerView` - 구현 내용: - `lives`가 비면 라이브 섹션 숨김 - `banners`가 비면 배너 섹션 숨김 - 이미지 로딩은 `ImageView.loadUrl()` 또는 기존 widget adapter의 방식 사용 - 검증: 샘플 `lives`, `banners` 데이터로 실제 기기에서 라이브 가로 목록과 배너 캐러셀이 의도한 위치/크기로 보인다. 빈 리스트일 때 title/RecyclerView가 함께 `GONE`. - [x] **Task 6.2: 최근 활동/최근 데뷔 크리에이터 UI 구현** - 생성 후보: `app/src/main/res/layout/item_home_recent_activity_creator.xml` - 생성 후보: `app/src/main/res/layout/item_home_recent_debut_creator.xml` - 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecentActivityCreatorAdapter.kt` - 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecentDebutCreatorAdapter.kt` - 구현 내용: - 최근 활동 activity type label은 Phase 3 mapper 결과 사용 - unknown activity type은 label view 숨김 - 닉네임/제목은 기존 typography와 말줄임 정책 적용 - 검증: 샘플 creator 데이터로 실제 기기에서 최근 활동/최근 데뷔 카드 배치가 보인다. `LIVE_REPLAY`도 `라이브`로 표시. - [x] **Task 6.3: 첫 오디오 콘텐츠와 AI 캐릭터 섹션 바인딩** - 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFirstAudioAdapter.kt` - 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeAiCharacterAdapter.kt` - 재사용: `AudioContentCardView`, `CharacterChatThumbnailView` - 구현 내용: - 첫 오디오 콘텐츠는 `AudioContentTag.First` 항상 포함 - `isPointAvailable == true`면 `AudioContentTag.Point` - `price == 0`이면 `AudioContentTag.Free` - `AudioContentTag.Original`은 포함하지 않음 - AI character image는 `profileImage` 사용 - 검증: 샘플 audio/AI character 데이터로 실제 기기에서 카드 배치와 이미지/텍스트 영역이 보인다. 오디오 태그 조합이 PRD 조건과 일치. - [x] **Task 6.4: 장르별/응원 크리에이터와 모두 팔로우 버튼 구현** - 현재 정책: 2026-07-12 Phase 14 이후 `genreCreators` UI와 모두 팔로우 버튼은 추천 탭에 표시하지 않으며, 아래 장르 관련 구현 내용은 과거 이력으로만 유지한다. - 생성 후보: `app/src/main/res/layout/item_home_creator_profile.xml` - 생성 후보: `app/src/main/res/layout/view_home_follow_all_button.xml` - 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt` - 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeCheerCreatorAdapter.kt` - 구현 내용: - `cheerCreators.creatorId` 목록으로 모두 팔로우 API 호출 - 현재 미사용: `genreCreators`는 추천 탭 UI에 표시하지 않으므로 해당 그룹 `creators.creatorId` 목록으로 모두 팔로우 API를 호출하지 않는다. - success 후 `모두 팔로우 완료`, `ic_new_following`, click disabled - 실패 시 toast/error 표시 - 검증: 현재 정책에서는 `genreCreators` UI 미표시를 검증하고, `cheerCreators` profile grid와 모두 팔로우 버튼 배치는 기존 정책대로 유지한다. 빈 creator list에서는 API 호출하지 않고 버튼 상태 유지. - [x] **Task 6.5: 방금 활동한 크리에이터 Figma 정합 수정** - 기준: Figma `24:5529` - 수정: `app/src/main/res/layout/item_home_recent_activity_creator.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePhase6Adapters.kt` - 생성: `app/src/main/res/drawable/bg_home_recent_activity_card.xml` - 생성: `app/src/main/res/drawable/bg_home_recent_activity_type.xml` - 구현 내용: - 세로 프로필형 카드를 `244dp x 76dp` 가로 capsule 카드로 변경 - profile image를 `52dp x 52dp`로 변경 - 닉네임은 `18sp` bold, activity type은 `gray_700` tag, 보조 텍스트는 `activityAt` 표시 - 검증: `HomeMainFragmentLayoutTest`에 Figma capsule 치수 회귀 테스트를 추가한다. - [x] **Task 6.6: 홈 추천 섹션 adapter 파일 분리** - 삭제: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePhase6Adapters.kt` - 생성: `HomeLiveAdapter.kt`, `HomeBannerBinder.kt`, `HomeRecentActivityCreatorAdapter.kt`, `HomeRecentDebutCreatorAdapter.kt`, `HomeFirstAudioAdapter.kt`, `HomeAiCharacterAdapter.kt`, `HomeCreatorProfileAdapter.kt`, `HomeGenreCreatorAdapter.kt`, `HomeCheerCreatorAdapter.kt`, `HomeFollowAllButtonBinder.kt`, `HomeRecyclerItemLayoutParams.kt` - 구현 내용: - 한 파일에 모여 있던 섹션별 adapter/binder를 섹션 단위 파일로 분리 - 공통 `RecyclerView.LayoutParams` 생성 로직은 package-private helper로 분리 - UI 동작과 바인딩 계약은 변경하지 않음 - 검증: `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest --tests kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest`, `./gradlew :app:ktlintCheck`를 실행한다. - [x] **Task 6.7: 라이브 섹션 Figma 정합 수정** - 기준: Figma `24:5516` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/res/values/dimens.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `rv_home_lives` 높이는 아이템 높이에 따라가도록 `wrap_content` 유지 - `rv_home_lives` 시작 padding `20dp` 적용 - 라이브 아이템 간격을 Figma 기준 `14dp`로 적용 - 라이브는 최대 20개까지 표시하고, 20개 초과 시 `HomeLiveAdapter` 마지막 item으로 `58dp x 102dp` 검은 배경 `전체` item 추가 - 검증: `HomeMainFragmentLayoutTest`에 live row `wrap_content`, live adapter item gap, 20개 초과 시 `전체` item 추가 회귀 테스트를 추가한다. - [x] **Task 6.8: 최근 데뷔한 크리에이터 Figma 정합 수정** - 기준: Figma `24:5534` - 수정: `app/src/main/res/layout/item_home_recent_debut_creator.xml` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecentDebutCreatorAdapter.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 생성: `app/src/main/res/drawable/bg_home_recent_debut_card.xml` - 생성: `app/src/main/res/drawable/bg_home_recent_debut_dim_gradient.xml` - 구현 내용: - 기존 원형 profile item을 `205dp x 259dp` 이미지 카드로 변경 - 카드 radius `14dp`, 하단 dim gradient, 닉네임 `24sp` bold 중앙 정렬 적용 - `rv_home_recent_debut_creators` 시작 padding `14dp`, item gap `4dp` 적용 - 검증: `HomeMainFragmentLayoutTest`에 recent debut 카드 치수와 목록 간격 회귀 테스트를 추가한다. - [x] **Task 6.8.1: 최근 데뷔한 크리에이터 item 축소 및 root clipping 보완** - 기준: Figma `24:5534`, item `24:5537` - 수정: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md` - 수정: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - 수정: `app/src/main/res/layout/item_home_recent_debut_creator.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecentDebutCreatorAdapter.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - item root를 기존 `205dp x 259dp` 비율 기준 `185dp x 234dp`로 축소한다. - `ImageView`는 root 전체를 채우도록 `match_parent`로 맞춘다. - adapter ViewHolder에서 item root에 `clipToOutline=true`와 `ViewOutlineProvider.setRoundRect(..., radius_14)`를 적용해 이미지, dim gradient, 닉네임 전체가 `14dp` radius 안에서 잘리게 한다. - 검증: `HomeMainFragmentLayoutTest`의 recent debut 카드 테스트를 먼저 `185dp x 234dp`, adapter root clipping 기대값으로 바꿔 RED를 확인한 뒤 동일 테스트를 GREEN으로 전환한다. - [x] **Task 6.9: 처음부터 함께 성장! Figma 정합 수정** - 기준: Figma `24:5539` - 생성: `app/src/main/res/layout/item_home_first_audio_content.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFirstAudioAdapter.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecyclerItemLayoutParams.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `AudioContentCardView` 재사용 대신 첫 오디오 전용 item layout을 사용한다. - 썸네일을 `185dp x 185dp`, radius `14dp` 카드로 구성한다. - creator profile을 `42dp x 42dp`, creator nickname `14sp`로 구성하고 상세 시간은 표시하지 않는다. - `rv_home_first_audio_contents` 시작 padding `14dp`, item gap `4dp`를 적용한다. - `First`, `Point`, `Free` tag 표시 조건은 기존 UI model의 `tags`를 그대로 사용한다. - 검증: `HomeMainFragmentLayoutTest`에 first audio 카드 치수, 목록 간격, tag visibility 회귀 테스트를 추가한다. - [x] **Task 6.10: AI 캐릭터 섹션 Figma 정합 수정** - 기준: Figma `24:5551` - 확인: `app/src/main/res/layout/view_character_chat_thumbnail.xml` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeAiCharacterAdapter.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `크리에이터와 이야기를 나눠요!` 목록 시작/끝 padding을 `14dp`로 적용한다. - AI 캐릭터 item gap을 `4dp`로 적용한다. - adapter에서 `view_character_chat_thumbnail.xml`의 root `185dp x wrap_content` 치수를 generic layout params로 덮어쓰지 않는다. - 검증: `HomeMainFragmentLayoutTest`에 AI 캐릭터 목록 spacing과 adapter item dimension 회귀 테스트를 추가한다. - [x] **Task 6.11: 장르 크리에이터 모두 팔로우 버튼 Figma 정합 수정** - 기준: Figma `24:5611`, 완료 버튼 `24:9092` - 수정: `app/src/main/res/layout/view_home_follow_all_button.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowAllButtonBinder.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - 기본 상태는 `모두 팔로우하기`, `ic_new_follow`, 투명 배경과 흰색 30% stroke, 흰색 텍스트를 적용한다. - 완료 상태는 `모두 팔로우 완료`, `ic_new_following`, 흰색 배경, 검은색 텍스트를 적용한다. - 완료 상태에서는 터치해도 callback을 호출하지 않는다. - 검증: `HomeMainFragmentLayoutTest`에 모두 팔로우 기본/완료 상태와 완료 상태 no-op 회귀 테스트를 추가한다. - [x] **Task 6.12: 장르의 크리에이터 Figma 정합 추가 수정** - 현재 정책: 2026-07-12 Phase 14 이후 `genreCreators` 그룹 page/snapping UI는 추천 탭에 표시하지 않으며, 아래 표시 요구는 과거 구현 이력으로만 유지한다. - 기준: Figma `24:5611` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt` - 생성 후보: `app/src/main/res/layout/item_home_genre_creator_group.xml` - 생성 후보: `app/src/main/res/layout/item_home_genre_creator_profile.xml` - 생성 후보: `app/src/main/res/drawable/bg_home_genre_creator_group.xml` - 구현 내용: - 현재 미사용: `genreCreators: List`를 그룹 단위 page로 표시하지 않는다. - 현재 미사용: 장르 page 치수/배경 요구는 추천 탭 UI에 적용하지 않는다. - 현재 미사용: 장르 제목, creator profile grid, 모두 팔로우 버튼을 포함한 page를 표시하지 않는다. - 현재 미사용: 장르 page 목록 snapping을 제공하지 않는다. - 현재 미사용: 장르 group 표시/최대 개수 정책은 추천 탭 UI에 적용하지 않는다. - 검증: 현재 정책에서는 `HomeMainFragmentLayoutTest`에서 `genreCreators` 섹션 기본 숨김, 바인딩 시 `GONE`, hidden section 제외 empty 판정을 검증한다. - [x] **Task 6.13: 최근 응원이 많은 크리에이터 Figma 정합 추가 수정** - 기준: Figma `24:5636` - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeCheerCreatorAdapter.kt` - 생성 후보: `app/src/main/res/layout/item_home_cheer_creator_group.xml` - 생성 후보: `app/src/main/res/drawable/bg_home_cheer_creator_group.xml` - 구현 내용: - 섹션은 특정 width로 고정하지 않고 화면 폭을 채우며 horizontal padding으로 card 폭을 조정한다. - `최근 응원이 많은 크리에이터` card는 `14dp` padding/radius/gap, `gray_900` 배경을 적용한다. - creator profile은 장르 크리에이터와 유사한 simple profile grid로 표시하고 최대 8명만 노출한다. - 모두 팔로우 버튼은 card 내부 하단에 포함하고 기존 기본/완료 상태를 유지한다. - 검증: `HomeMainFragmentLayoutTest`에 cheer card/grid/width/follow callback 회귀 테스트를 추가한다. --- ### Phase 7: FeedCommunityView 추천 페이지 확장 - [x] **Task 7.1: FeedItem.Community 추천 필드 확장** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedItem.kt` - 추가 후보: - `imageUrl: String?` - `price: Int` - `existOrdered: Boolean` - `showKeyword: Boolean` - 주의: 기존 호출부 기본값을 제공해 기존 feed 사용처 영향 최소화. - 검증 명령: `./gradlew :app:compileDebugKotlin` - [x] **Task 7.2: 커뮤니티 이미지/유료 overlay 레이아웃 추가** - 수정: `app/src/main/res/layout/view_feed_community.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedAdapter.kt` - 구현 내용: - keyword는 추천 페이지에서 숨김 - `imageUrl != null`이면 `346dp x 236dp`, radius `14dp`, centerCrop 이미지 표시 - `price > 0 && existOrdered == false`면 blur/lock overlay와 가격 capsule 표시 - 무료/구매 완료는 overlay와 가격 capsule 숨김 - `FeedImageViews.primary`로 커뮤니티 이미지도 외부에서 로드 가능하게 노출 - 검증: 기존 Community variant는 기본값으로 keyword를 유지하고, 추천용 item만 keyword를 숨긴다. - [x] **Task 7.3: 인기 커뮤니티 섹션 바인딩** - 생성 후보: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePopularCommunityAdapter.kt` - 재사용: `FeedAdapter` 또는 `FeedCommunityView` - 구현 내용: - `postId`, `creatorId`, `creatorNickname`, `creatorProfileImage`, `content`, `createdAt`, `likeCount`, `commentCount`, `imageUrl`, `price`, `existOrdered` 매핑 - `audioUrl`은 별도 플레이어 UI 없이 상세 이동 데이터로만 유지 - 검증: keyword 미노출, 이미지 null이면 이미지 영역 `GONE`. - [x] **Task 7.4: 실기기 확인용 인기 커뮤니티 샘플 데이터 주입** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 구현 내용: - 기존 `phase6SampleContent()`의 `popularCommunityPosts` 빈 목록을 무료/유료 미구매/구매 완료 샘플 3건으로 교체 - 추천용 커뮤니티 샘플은 `showKeyword = false`로 유지 - 유료 미구매 샘플은 lock overlay와 가격 capsule 확인용으로 구성 - 주의: Phase 9 실제 API/ViewModel 바인딩 시 임시 샘플 데이터는 실제 상태 바인딩으로 교체한다. - 검증: 실제 기기에서 인기 커뮤니티 섹션과 카드 상태 3종을 육안 확인할 수 있다. - [x] **Task 7.5: 커뮤니티 이미지 고정 크기 제거와 동적 비율 적용** - 수정: `app/src/main/res/layout/view_feed_community.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedCommunityView.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/feed/FeedViewTest.kt` - 구현 내용: - `FeedCommunityView` root와 이미지 container의 고정 폭을 제거하고 `match_parent`로 변경 - 이미지 container 높이는 실제 card content width 기준으로 Figma `346:236` 비율을 유지해 계산 - Kotlin `clipToOutline`/`ViewOutlineProvider.setRoundRect(...)` clipping 계약 유지 - 검증: 인기 커뮤니티 이미지 영역이 화면 폭에 맞춰 계산되어 고정 폭 때문에 잘리지 않는다. #### Task R7.1: 유료 미구매 원본 이미지 로드 회귀 수정 **Goal 실행 `P7-R1`:** 유료 미구매 게시물에서 원본 `imageUrl`을 다시 로드하는 회귀를 막고, 원본 미로드 상태에서도 기존 lock overlay와 가격 표시를 유지하며 요구사항·코드·테스트·검증 기록을 일치시킨다. - **시작 조건:** `REV-P7-001` 확정 및 `reviews/phase7-popular-community-review.md` 판정 완료. - **완료 증거:** 현재 코드에서 실패하는 원본 미로드 회귀 테스트, 최소 수정 후 focused test와 home 회귀·compile·ktlint 성공, PRD/plan 정합성 정정, 리뷰 보고서 수정 후 검증 기록 누적. - **범위 밖:** API·DTO에 서버 preview 필드 추정 추가, 구매 권한 판정 변경, `FeedCommunityView` 공통 레이아웃 리팩터링, 기존 완료 기록 삭제 또는 덮어쓰기. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePopularCommunityAdapter.kt` - Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase7-popular-community-review.md` - [x] **RED:** 유료 미구매 item의 원본 URL을 image loader에 전달하지 않고 기존 요청·drawable을 비우며 lock overlay/가격은 유지하는 회귀 테스트를 먼저 추가해 현재 구현에서 의도한 실패를 확인한다. - [x] **GREEN:** `HomePopularCommunityAdapter`만 최소 수정해 무료·구매 완료 이미지는 기존대로 로드하고 유료 미구매 원본 이미지는 로드하지 않도록 복구한다. - [x] 현재 PRD의 blur 문구를 원본 미로드 보안 결정과 일치시키고, commit `2d58a876`에서 발생한 회귀 및 이번 복구 이력을 기존 기록을 삭제하지 않은 채 PRD와 `Verification Log`에 누적한다. - [x] focused test, `HomeMainFragmentLayoutTest` 전체, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check`를 실행한다. - [x] `REV-P7-001` 보고서의 `수정 후 검증 기록`에 변경 파일, 검증 명령, 결과와 남은 항목을 누적한다. - 검증 기록: 2026-07-31 RED는 `HomeMainFragmentLayoutTest.home popular community adapter does not load original image for locked paid post`를 먼저 추가하고 focused test를 실행해 `HomeMainFragmentLayoutTest.kt:1032` assertion 실패로 확인했다. GREEN은 `HomePopularCommunityAdapter`에서 잠금 유료 게시물일 때 `imageUrl`을 전달하지 않고 `BlurTransformation` 경로를 제거하는 최소 변경으로 동일 focused test BUILD SUCCESSFUL을 확인했다. `FeedCommunityView.bind(item.item)` 호출은 유지해 lock overlay와 가격 capsule은 기존 View 계약을 따른다. - 검증 기록: 2026-07-31 후속 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`를 순차 실행해 모두 성공했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고다. `REV-P7-001` 보고서의 `수정 후 검증 기록`에 변경 파일, 검증 명령, 결과, 남은 항목 없음 상태를 누적했다. #### Task R7.2: 인기 커뮤니티 회귀 테스트 이름 규칙 정정 **Goal 실행 `P7-R2`:** `REV-P7-002`의 현재 영문 테스트명을 한글 문장으로 정정하고 원본 미로드 회귀 assertion을 그대로 유지한다. - **시작 조건:** `REV-P7-002` 확정 및 `P7-R1` 수정 검증 완료. - **완료 증거:** 테스트명을 `유료 미구매 인기 커뮤니티는 원본 이미지를 로드하지 않는다`로 정정, 동일 focused test·`ktlintCheck`·`git diff --check` 성공, 리뷰 보고서 검증 기록 누적. - **범위 밖:** production adapter 동작, assertion 완화, 다른 기존 영문 테스트명 일괄 정리. - **TDD 예외 사유:** 기존 회귀 테스트의 표현만 정정하는 코드 품질 Task이므로 RED/GREEN 대신 정정 전·후 동일 assertion 성공을 검증한다. **Files:** - Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase7-popular-community-review.md` - [x] 현재 테스트명과 `code-style.md` 규칙 위반을 검색으로 재현한다. - [x] assertion과 production code는 바꾸지 않고 대상 테스트명만 한글로 정정한다. - [x] 변경된 메서드 focused test, `./gradlew :app:ktlintCheck`, `git diff --check`를 실행하고 결과를 누적한다. - 검증 기록: 2026-07-31 `rg -n "home popular community adapter does not load original image for locked paid post|cheer creator follow all opens confirm dialog before api request|follow all confirm strings are localized" app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt docs/agent-guides/code-style.md`로 신규 영문 테스트명과 `code-style.md` 한글 테스트명 규칙 위반을 재현했다. Production code와 assertion은 바꾸지 않고 대상 테스트명을 `유료 미구매 인기 커뮤니티는 원본 이미지를 로드하지 않는다`로 정정했다. - 검증 기록: 2026-07-31 변경된 메서드 focused test `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.유료 미구매 인기 커뮤니티는 원본 이미지를 로드하지 않는다" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.응원 크리에이터 모두 팔로우는 API 요청 전에 확인창을 연다" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.모두 팔로우 확인창 문자열은 다국어로 제공한다"`, 직접 영향 회귀 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialogTest"`, `./gradlew :app:ktlintCheck`, `git diff --check`를 실행했고 모두 성공했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고다. #### Task R7.3: 유료 미구매 이미지 미로드 실행형 회귀 테스트 보강 **Goal 실행 `P7-R3`:** `REV-P7-003`의 소스 문자열 검사를 실제 adapter bind 동작 회귀 테스트로 대체해, 유료 미구매 이미지의 원본 미로드·기존 요청 해제·drawable 제거·lock overlay·가격 표시 계약을 실행 결과로 보호한다. - **시작 조건:** `REV-P7-003` 확정 및 `reviews/phase7-popular-community-review.md` 판정 완료. - **완료 증거:** 잠금 유료 item을 `HomePopularCommunityAdapter` ViewHolder에 bind하는 실행형 테스트, 기존 image request/drawable 제거와 overlay/가격 유지 assertion, 무료·구매 완료 이미지 경로 회귀, focused·home·compile·ktlint·diff 검증 성공, 리뷰 보고서 결과 누적. - **범위 밖:** production 보안 정책 변경, 서버 preview 필드 추정, `FeedCommunityView` 공통 레이아웃 리팩터링, 기존 테스트 전체 재구성. - **TDD 예외 사유:** production 보안 회귀는 `P7-R1`에서 이미 RED/GREEN으로 수정됐고 이번 항목은 완료 증거의 실행성 보강이다. 현재 정상 동작에서 새 테스트가 통과하는 것을 기준으로 하며, 실행형 테스트가 추가 결함을 드러낸 경우에만 production 수정용 RED/GREEN으로 전환한다. **Files:** - Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` 또는 동일 패키지의 전용 adapter test - Modify only if RED 재현이 production 결함을 확정할 때: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePopularCommunityAdapter.kt` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase7-popular-community-review.md` - [x] **TEST:** 잠금 유료 item bind 전 image view에 기존 drawable/request가 있는 상태를 만들고 bind 후 원본 로드 미실행, 요청 해제, drawable 제거, lock overlay 표시, 가격 유지를 실행형 assertion으로 검증한다. - [x] **FIX IF NEEDED:** 실행형 테스트가 production 결함을 재현한 경우에만 별도 RED/GREEN 기록 후 adapter를 최소 수정하고, 현재 동작이 이미 계약을 충족하면 production은 변경하지 않는다. - [x] 무료·구매 완료 item의 이미지 로드 경로와 잠금 유료 item의 overlay/가격 표시 회귀를 함께 검증한다. - [x] focused test, `HomeMainFragmentLayoutTest` 또는 전용 adapter test 전체, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check`를 실행한다. - [x] `REV-P7-003` 보고서에 변경 파일, 실행형 테스트 결과, 필요 시 RED/GREEN 결과, 검증 명령, 남은 항목을 누적한다. - 검증 기록: 2026-07-31 기존 `HomeMainFragmentLayoutTest`의 production source 문자열 검사를 제거하고 `HomePopularCommunityAdapterTest`를 추가했다. 잠금 유료 item은 실제 adapter/ViewHolder bind에서 원본 URL `loadUrl` 호출 0회, 재사용 image view의 기존 Coil `Disposable` 해제, drawable 제거, lock overlay `VISIBLE`, 가격 `30`을 검증한다. 무료와 구매 완료 item은 각각의 원본 URL이 loader 경계에 정확히 1회 전달되는지 검증한다. 외부 이미지 로딩만 MockK static 경계로 차단하고 adapter, ViewHolder, `FeedCommunityView`, Coil 요청 해제는 실제 구현을 실행했다. - 검증 기록: 2026-07-31 테스트 효과 확인을 위해 `HomePopularCommunityAdapter`의 보안 분기를 일시적으로 `item.item.imageUrl` 직접 전달로 변경한 뒤 잠금 focused test를 실행했고, `HomePopularCommunityAdapterTest.kt:72`의 원본 `loadUrl` 호출 0회 assertion에서 1건 실패했다. 즉시 `takeIf { !item.item.isLockedPaidPost() }` 분기를 복구한 뒤 전용 test class 2건이 BUILD SUCCESSFUL로 다시 통과했다. 최종 production 동작은 `P7-R1` 상태와 같아 이번 Task에서 추가 production 수정은 남기지 않았다. - 검증 기록: 2026-07-31 `HomePopularCommunityAdapterTest`와 `HomeMainFragmentLayoutTest`, `:app:compileDebugKotlin`, `:app:ktlintCheck` 결합 명령은 BUILD SUCCESSFUL이었다. 전체 `./gradlew :app:testDebugUnitTest --no-parallel --console=plain`도 162 suites, 1,198 tests, 실패 0, error 0으로 BUILD SUCCESSFUL이었다. ktlint의 `.editorconfig disabled_rules`와 Gradle deprecation warning은 기존 경고다. 코드 재검토에서 추가 production 결함이나 신규 회귀 Task 전환 항목은 확인되지 않았다. --- ### Phase 8: 사업자 정보 접기/더보기 구현 - [x] **Task 8.1: 사업자 정보 UI 구현** - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 구현 내용: - 기본 `maxLines = 3`, `ellipsize = end` - `더보기` 클릭 시 전체 표시 - `접기` 클릭 시 다시 3줄 말줄임 - 3줄 이하 텍스트면 toggle 숨김 - 검증: 외부 라이브러리 없이 `TextView.post { lineCount }` 기반으로 toggle 노출 여부 결정. --- ### Phase 9: 라우팅, empty/error/loading 정책 연결 - [x] **Task 9.1: Fragment observe와 loading/toast 연결** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 구현 내용: - `HomeRecommendationViewModel` 주입 - `loadRecommendations()` 호출 - `recommendationStateLiveData` observe 후 각 섹션 bind - `isLoading`은 기존 `LoadingDialog` 또는 홈 화면 패턴 확인 후 적용 - `toastLiveData`는 `BaseFragment.showToast()` 사용 - 검증: API 실패 시 앱 크래시 없이 toast/error 처리. - [x] **Task 9.2: 클릭 라우팅 연결** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 확인: 기존 상세 화면 Activity/Fragment 라우팅 파일 - 구현 내용: - 배너 type별 이동은 기존 딥링크/이동 정책 확인 후 연결 - live, creator, audio content, AI character, community post 클릭은 adapter callback으로 Fragment에 위임 - 확정되지 않은 목적지는 임시 주석으로 남기지 않고 no-op 또는 기존 정책 확인 후 최소 연결 - 검증: UI 컴포넌트 내부에서 목적지를 결정하지 않는다. - [x] **Task 9.3: 홈 추천 응답 스키마 변경 대응** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeBannerBinder.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecentActivityCreatorAdapter.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFirstAudioAdapter.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeCheerCreatorAdapter.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - 변경된 `HomeLiveItem(roomId, creatorNickname, creatorProfileImage)`을 라이브 UI model과 `LiveThumbnailSimpleView` 바인딩에 반영한다. - 변경된 `HomeBannerItem(imageUrl, eventItem, creatorId, seriesId, link)`을 배너 UI model과 `BannerView` 바인딩에 반영한다. - 변경된 `HomeActiveCreatorItem(creatorNickname, creatorProfileImage, activityType, activityAt, targetId)`을 최근 활동 UI model/mapper에 반영하고, 응답에서 제거된 `creatorId` 기반 프로필 이동은 제거한다. - 변경된 `HomeCreatorItem(creatorNickname, creatorProfileImage)`, `HomeFirstAudioContentItem`의 `releaseDate` 제거, `HomeGenreCreatorGroupItem.genreName`을 mapper/UI model에 반영한다. - 실제 UI에서 사용하지 않거나 응답에서 더 이상 제공하지 않는 nullable placeholder 값은 UI model에 추가하지 않는다. - 검증: - `HomeMainFragmentLayoutTest`에 변경된 응답 필드 기반 mapper 회귀 테스트를 추가한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recommendation mapper uses changed response fields"`로 RED/GREEN을 확인한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`로 회귀를 확인한다. - [x] **Task 9.4: 홈 추천 이미지 캐시 ANR 대응** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/common/ImageLoaderProvider.kt` - 생성: `app/src/test/java/kr/co/vividnext/sodalive/common/ImageLoaderProviderTest.kt` - 구현 내용: - 홈 추천 API 바인딩 후 다수 이미지 로딩 시 `unexpected journal header` 경고와 ANR이 발생하는 현상을 `ImageLoaderProvider`의 OkHttp cache journal 충돌 가능성으로 분리한다. - 기존 `cacheDir/image_cache`를 legacy OkHttp image cache로 보고 앱 시작 시 삭제한다. - 신규 이미지 loader OkHttp cache는 `cacheDir/coil_image_cache`로 분리해 손상된 기존 journal을 다시 읽지 않도록 한다. - 검증: - `ImageLoaderProviderTest`에 legacy cache directory와 신규 cache directory가 분리되는 회귀 테스트를 추가한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.ImageLoaderProviderTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`로 회귀를 확인한다. - [x] **Task 9.5: BannerView 홈 추천 배너 모델 직접 바인딩** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerView.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerAdapter.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerItem.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeBannerBinder.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/banner/BannerViewTest.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `BannerItem`을 `HomeRecommendationBannerUiModel`과 동일한 배너 payload(`imageUrl`, `eventItem`, `creatorId`, `seriesId`, `link`)를 담는 위젯 전용 모델로 변경한다. - `BannerView`/`BannerAdapter`는 공용 위젯 독립성을 유지하기 위해 `BannerItem`만 사용하고, `HomeBannerBinder`에서 홈 추천 배너 UI model과 `BannerItem`을 변환한다. - `HomeBannerBinder`의 synthetic id 매칭 로직을 제거하고 클릭 시 `BannerItem` payload를 다시 `HomeRecommendationBannerUiModel`로 전달한다. - 2개 이상 배너에서 `BannerAdapter.itemCount == Int.MAX_VALUE`가 되더라도 `submitItems()`와 size/preview 갱신이 `notifyItemRangeInserted(0, Int.MAX_VALUE)` 또는 `notifyItemRangeChanged(0, Int.MAX_VALUE)` 같은 huge range notify를 호출하지 않도록 한다. - 단일 배너 상태에서는 `notifyItemInserted`/`notifyItemChanged`/`notifyItemRemoved`를 사용하고, `Int.MAX_VALUE` 가상 adapter 상태에서만 `NotifyDataSetChanged` lint를 좁은 helper에 제한해 전체 갱신을 수행한다. - 검증: - `BannerViewTest`에 carousel 설정 시 max range insert/change notify를 호출하지 않는 회귀 테스트와 단일 배너 specific notify 회귀 테스트를 추가한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`로 회귀를 확인한다. - [x] **Task 9.6: 홈 추천 프로필 이미지 원형 처리와 상대 시간 표시 적용** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/GetCommunityPostListResponse.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeRecentActivityCreatorAdapter.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeGenreCreatorAdapter.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeCheerCreatorAdapter.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - 기존 `GetCommunityPostListResponse.relativeTimeText(context)`가 사용하는 UTC 서버 시간 파싱과 상대 시간 문자열 계산을 DTO 전용 확장 함수에서 재사용 가능한 일반 함수 또는 공통 확장 함수로 분리한다. - `HomeActiveCreatorItem.activityAt`은 UTC 값을 디바이스 기본 타임존 기준으로 해석한 뒤 time-ago timestamp 문자열로 변환해 `HomeRecommendationRecentlyActiveCreatorUiModel.activityAt`에 매핑한다. - `HomePopularCommunityPostItem.createdAt`도 같은 상대 시간 변환 함수를 사용해 `FeedItem.Community.createdAtText`에 매핑한다. - `방금 활동한 크리에이터`, `[장르] 크리에이터`, `최근 응원이 많은 크리에이터`의 creator profile image는 기존 Coil image loading 패턴에 `CircleCropTransformation` 또는 동일한 원형 clipping 방식을 적용한다. - 상대 시간 변환 실패 시 크래시 없이 기존 커뮤니티 포스트 정책과 동일하게 `character_comment_time_just_now` 문자열을 표시한다. - 검증: - `HomeMainFragmentLayoutTest`에 최근 활동 `activityAt`과 인기 커뮤니티 `createdAt`이 UTC 입력에서 상대 시간 문자열로 매핑되는 회귀 테스트를 추가한다. - `HomeMainFragmentLayoutTest`에 최근 활동/장르/응원 creator profile image가 원형 처리 계약을 적용하는 회귀 테스트를 추가한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`로 회귀를 확인한다. --- ### Phase 10: 배너 이동 정책 연결 - [x] **Task 10.1: 홈 추천 배너 클릭 라우팅 구현** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 확인: 기존 `EventDetailActivity`, `UserProfileActivity`, `SeriesDetailActivity` 라우팅 extra 계약 - 구현 내용: - 배너 응답에서 `type` 값은 제거되었으므로 사용하지 않는다. - `eventItem != null`이면 `EventDetailActivity`로 이동한다. - `creatorId != null`이면 `UserProfileActivity`로 이동한다. - `seriesId != null`이면 `SeriesDetailActivity`로 이동한다. - `link != null && link가 웹 URL`이면 `Intent.ACTION_VIEW`를 사용해 외부 웹 URL로 이동한다. - `link != null && link가 내부 딥링크`이면 기존 딥링크 실행 정책에 따라 앱 내부 딥링크를 실행한다. - 여러 이동 대상 값이 함께 내려오면 위 순서대로 우선 처리한다. - 웹 URL도 내부 딥링크도 아닌 `link`는 이동하지 않는다. - 검증: - `HomeMainFragmentLayoutTest` 또는 라우팅 단위 테스트에 배너 목적지별 Intent 계약 회귀 테스트를 추가한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`로 회귀를 확인한다. --- ### Phase 11: 방금 활동한 크리에이터 터치 액션 연결 - [x] **Task 11.1: 방금 활동한 크리에이터 클릭 라우팅 구현** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `Live`는 터치 액션 없음. - `LiveReplay`, `Audio`는 `AudioContentDetailActivity`로 이동하고 `Constants.EXTRA_AUDIO_CONTENT_ID`에 `targetId`를 전달한다. - `Community`는 `CreatorCommunityAllActivity`로 이동하고 `Constants.EXTRA_COMMUNITY_CREATOR_ID`에 `targetId`를 전달한다. - `activityType`이 unknown이거나 `targetId`가 없거나 0 이하이면 이동하지 않는다. - 검증: - `HomeMainFragmentLayoutTest`에 activity type별 route와 Intent extra 계약 회귀 테스트를 추가한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recent activity route maps activity type to target destination" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recent activity route ignores live unknown and missing target" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recent activity route creates expected intents"`로 RED/GREEN을 확인한다. - [x] **Task 11.2: AI 캐릭터 item 크리에이터 채널 이동으로 변경** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `HomeAiCharacterItem`에 `@SerializedName("creatorId") val creatorId: Long`을 추가한다. - `HomeRecommendationAiCharacterUiModel` 또는 동등한 UI model에 `creatorId`를 보존한다. - `HomeAiCharacterItem.toUiModel()`에서 `characterId`는 `CharacterChatThumbnailItem.characterId` 표시 계약에 유지하고, `creatorId`는 클릭 라우팅 전용 값으로 분리해 전달한다. - `HomeAiCharacterAdapter` callback은 AI 캐릭터 UI model을 그대로 전달하고, `HomeMainFragment`에서 `creatorId > 0L`일 때 `CreatorChannelActivity.newIntent(requireContext(), creatorId)`로 이동한다. - 기존 `CharacterDetailActivity` import와 `openCharacterDetail()` 사용은 제거한다. - `creatorId <= 0L`이면 앱이 crash 되지 않고 이동하지 않는다. - 검증: - `HomeMainFragmentLayoutTest`에 `HomeAiCharacterItem.creatorId`가 UI model로 보존되는 mapper 회귀 테스트를 추가한다. - `HomeMainFragmentLayoutTest`에 AI 캐릭터 item route가 `CreatorChannelActivity` intent와 `creatorId` extra를 생성하고, `creatorId <= 0L`에서는 intent를 생성하지 않는 회귀 테스트를 추가한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`로 RED/GREEN을 확인한다. - `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`로 회귀를 확인한다. --- ### Phase 12: 최종 검증과 문서 기록 - [x] **Task 12.1: 단위 테스트와 빌드 검증** - 실행: `./gradlew :app:testDebugUnitTest` - 실행: `./gradlew :app:compileDebugKotlin` - 실행: `./gradlew :app:mergeDebugResources` - 실행: `./gradlew :app:ktlintCheck` - 기대 결과: 모두 성공. - [x] **Task 12.2: 수동 확인** - 확인 항목: - 추천 tab 선택 상태 - tab 전환은 글자 터치로만 동작하고 swipe 전환은 없음 - 세로 스크롤 시 title-bar와 `TextTabBarView`는 유지되고 `TextTabBarView` 아래 추천 content만 스크롤됨 - 섹션 순서가 PRD 도식과 일치 - 제외 섹션 미노출 - 빈 리스트 섹션 숨김 - Phase 12B에서 제거된 첫 오디오 섹션과 관련 태그 미노출 - activity type 다국어 문자열 - 방금 활동한 크리에이터 목적지별 클릭 이동 - AI 캐릭터 item 크리에이터 채널 클릭 이동 - 모두 팔로우 success 후 완료 상태 - 배너 목적지별 클릭 이동 - 인기 커뮤니티 유료/무료/구매 완료 이미지 상태 - 사업자 정보 더보기/접기 - 기대 결과: PRD Metrics 항목 충족. - [x] **Task 12.3: 검증 기록 누적** - 수정: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - 수정: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md` - 구현 내용: 실행 명령, 결과, 실패 시 원인과 후속 조치를 문서 하단 `검증 기록`에 누적한다. - 주의: 기존 검증 기록은 삭제하거나 덮어쓰지 않는다. #### Task R12.1: 연결 기기 확인 후 수동 검증 재개 **Goal 실행 `P12-R1`:** `REV-P12-001`의 오래된 `연결된 Android 기기 없음` blocker를 현재 환경에 맞게 정정하고 Task 12.2 수동 확인을 재개한다. - **시작 조건:** `REV-P12-001` 확정, `adb devices`에서 사용 가능한 기기 1대 확인, Task 12.1 자동 검증 성공. - **완료 증거:** Task 12.2의 홈 추천 항목을 실제 기기에서 확인, 항목별 결과·불가 사유·캡처 또는 로그 등 근거 누적, Phase 12 리뷰 판정 갱신. - **범위 밖:** 수동 Gate 통과를 위한 production 변경, 서버 추천 데이터 임의 조작, 다른 홈 tab 기능 수정. - **TDD 예외 사유:** 실기기 UI·상호작용 확인 Task이므로 기존 자동 검증을 선행 조건으로 사용하고 항목별 수동 증거로 완료를 판정한다. **Files:** - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-final-verification-review.md` - [x] `adb devices`를 재실행해 기기 연결 상태를 기록하고 Task 12.2의 기존 blocker 문구를 현재 상태로 정정한다. - [x] Task 12.2 추천 tab·스크롤·섹션·라우팅·사업자 정보 항목을 실기기에서 확인하고 각 항목의 결과를 누적한다. - [x] 수동 확인 결과에 따라 Task 12.2와 Phase 12 리뷰 종료 판정을 갱신하되 기존 기록은 삭제하지 않는다. - 검증 기록: 2026-07-31 `adb devices`에서 `2cec640c34017ece device`를 확인해 기존 `연결된 Android 기기 없음` blocker는 현재 상태와 다름을 확인했다. `./gradlew :app:installDebug`는 SM-G960N 기기에 설치 성공했고, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`도 성공했다. 그러나 `adb shell dumpsys window`는 foreground가 `kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.splash.SplashActivity`임을 반환했고, `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive_r12_manual.png` 및 retry `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive_r12_manual_retry.png`는 모두 검은 화면으로 판독됐다. 따라서 Task 12.2 추천 tab·스크롤·섹션·라우팅·사업자 정보 실기기 항목은 홈 추천 화면 접근 불가로 계속 blocked 처리한다. - 검증 기록: 2026-07-31 재검증에서 `adb devices`는 `2cec640c34017ece device`를 반환했고 `./gradlew :app:installDebug`, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`는 성공했다. `adb shell dumpsys window`는 foreground가 `kr.co.vividnext.sodalive.debug/kr.co.vividnext.sodalive.v2.main.MainV2Activity`임을 반환했지만, `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive_manual_gate_20260731/screen.png`는 검은 배경의 splash/voice on 화면으로 판독됐다. 홈 추천 화면과 Phase 17 확인창까지 접근하지 못해 Task 12.2 및 P17-GATE 수동 항목은 계속 blocked로 유지한다. - 검증 기록: 2026-07-31 사용자 수동 확인으로 추천 tab 선택, tab 터치 전환과 swipe 미전환, title/tab bar 고정과 content 스크롤, 제외 섹션 미노출, 빈 리스트 섹션 숨김, first audio 섹션/태그 미노출, 최근 활동/AI 캐릭터/배너 라우팅, 인기 커뮤니티 이미지 상태, 사업자 정보 더보기/접기가 모두 동작함을 확인했다. #### Task R12.2: 수동 확인의 first audio 제거 계약 동기화 **Goal 실행 `P12-R2`:** `REV-P12-002`의 미완료 Task 12.2가 제거된 first audio 표시 계약을 요구하지 않고 Phase 12B의 미노출 계약을 검증하도록 정정한다. - **시작 조건:** `REV-P12-002` 확정, `P12B-T1`·`P12B-T2` production 제거 완료 유지, Task 12.2 수동 확인 blocked 상태. - **완료 증거:** Task 12.2의 `첫 오디오 태그 조건`을 `첫 오디오 섹션과 관련 태그 미노출`로 정정, production 금지 참조 및 문서 활성 항목 검색, `git diff --check` 성공, 리뷰 보고서 검증 기록 누적. - **범위 밖:** Task 12.2 수동 항목 실행, production·resource·test 변경, 과거 Phase 6·9 이력 삭제. - **TDD 예외 사유:** 이미 완료된 production 제거에 대한 미완료 수동 체크리스트만 정정하므로 `rg` 대조와 문서 diff를 대체 검증으로 사용한다. **Files:** - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-final-verification-review.md` - [x] Task 12.2의 활성 first audio 표시 확인 항목과 Phase 12B 제거 계약의 불일치를 `rg`로 재현한다. - [x] 과거 검증 이력은 보존하고 Task 12.2 활성 항목만 first audio 미노출 검증으로 정정한다. - [x] production 금지 참조 및 문서 활성 항목 검색과 `git diff --check`를 실행하고 결과를 누적한다. - 검증 기록: 2026-07-31 `rg -n "firstAudioContents|HomeFirstAudioContentItem|releaseDate|첫 오디오|처음부터 함께 성장" docs/20260601_메인_홈_추천_UI와_API_연동/prd.md docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md app/src/main/java/kr/co/vividnext/sodalive/v2/main/home app/src/main/res/values app/src/main/res/layout`로 Task 12.2의 활성 `첫 오디오 태그 조건`과 Phase 12B 제거 계약이 추돌함을 재현했다. 해당 항목을 `Phase 12B에서 제거된 첫 오디오 섹션과 관련 태그 미노출`로 정정했고 production/resource 제거 계약은 유지됨을 확인했다. `git diff --check`는 출력 없이 성공했다. --- ## 확인 완료 사항 - 배너 이동은 `type` 없이 `eventItem`, `creatorId`, `seriesId`, `link` 값 기준으로 처리하며, `link`는 웹 URL이면 외부 브라우저, 내부 딥링크이면 앱 내부 딥링크로 실행한다. 세부 구현은 Phase 10에서 진행한다. - 시간 표시는 `RelativeTimeFormatter`에 재사용 가능한 함수가 있으므로 `activityAt`, `createdAt` 변환에 해당 formatter를 사용한다. - 모두 팔로우 API는 success response의 `data` 형태에 의존하지 않고 `ApiResponse.success == true`만으로 완료 처리한다. - 사업자 정보 텍스트는 서버/설정값이 아니라 `strings.xml`에 정의된 문자열을 사용한다. --- ## 검증 기록 - 2026-06-23: Task 11.2와 Phase 12 코드 리뷰 및 검증을 재수행했다. 리뷰 범위는 `HomeAiCharacterItem.creatorId` DTO 추가, `HomeRecommendationAiCharacterUiModel.creatorId` 보존, `CharacterChatThumbnailItem.characterId` 표시 계약 유지, `HomeRecommendationAiCharacterRoute.Creator` 라우팅, `CreatorChannelActivity.newIntent(context, creatorId)` intent 생성, `creatorId <= 0L` no-op, `CharacterDetailActivity` 제거, `HomeAiCharacterAdapter` callback 변경, 회귀 테스트 추가다. 코드 리뷰 결과 blocking finding은 없었다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`는 BUILD SUCCESSFUL을 확인했다. 첫 `./gradlew :app:testDebugUnitTest` 실행은 `CreatorChannelHomeViewModelTest > 채널 후원 성공은 기존 후원 API를 호출하고 홈을 다시 로드한다`의 `SharedPreferenceManager.can` assert에서 1건 실패했으나, 이번 변경 파일과 무관한 `v2.creator.channel` 영역이고 해당 테스트 단독 실행 및 `CreatorChannelHomeViewModelTest` 클래스 실행은 모두 BUILD SUCCESSFUL이었다. 이후 전체 `./gradlew :app:testDebugUnitTest` 재실행도 BUILD SUCCESSFUL을 확인했다. `./gradlew :app:compileDebugKotlin`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`도 BUILD SUCCESSFUL을 확인했다. `adb devices`는 권한 실행 기준 연결된 Android 기기가 없어 Phase 12.2 실제 화면 수동 확인은 계속 `[blocked: 연결된 Android 기기 없음]`으로 유지한다. - 2026-06-23: Phase 12.1 후속 조치로 기존 `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt:78`의 긴 줄을 동작 변경 없이 줄바꿈만 수정했다. 관련 테스트 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.fantalk.CreatorChannelFanTalkFragmentLayoutTest"`와 `./gradlew :app:ktlintCheck`는 BUILD SUCCESSFUL을 확인했다. 이후 Phase 12.1 전체 명령 `./gradlew :app:testDebugUnitTest`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`를 재실행했고 모두 BUILD SUCCESSFUL을 확인해 Task 12.1을 완료 처리했다. - 2026-06-23: Phase 12.2 수동 확인을 위해 `adb devices`를 실행했으나 연결된 Android 기기가 없어 실제 앱 화면 수동 확인은 수행하지 못했다. 자동 검증으로 추천 tab/스크롤/섹션/라우팅/빈 리스트/태그/배너/커뮤니티/사업자 정보 관련 `HomeMainFragmentLayoutTest` 전체와 전체 unit test는 통과했지만, 실제 기기 수동 확인 항목은 `[blocked: 연결된 Android 기기 없음]`으로 남긴다. - 2026-06-23: Phase 12.1 검증으로 `./gradlew :app:testDebugUnitTest`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:mergeDebugResources`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `./gradlew :app:ktlintCheck`는 이번 변경 파일이 아닌 `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt:78`의 기존 줄 길이/인자 줄바꿈 위반으로 실패했다. `git diff -- app/src/test/java/kr/co/vividnext/sodalive/v2/creator/channel/fantalk/CreatorChannelFanTalkFragmentLayoutTest.kt` 결과 변경 없음으로 확인해 이번 Task 11.2 변경과 분리했다. 따라서 Phase 12.1은 ktlint 외부 이슈 해소 전까지 완료 처리하지 않는다. - 2026-06-23: Task 11.2 구현으로 `HomeAiCharacterItem.creatorId`를 DTO에 추가하고, `HomeRecommendationAiCharacterUiModel`에 `creatorId`를 보존하도록 매핑했다. `characterId`는 기존 `CharacterChatThumbnailItem.characterId` 표시 계약에 유지하고, AI 캐릭터 item 클릭 라우팅은 `HomeRecommendationAiCharacterRoute.Creator`와 `CreatorChannelActivity.newIntent(context, creatorId)`로 분리했다. `HomeAiCharacterAdapter` callback은 UI model을 전달하도록 변경했고, `HomeMainFragment`에서는 `creatorId > 0L`일 때만 이동하며 기존 `CharacterDetailActivity` import와 `openCharacterDetail()` 사용을 제거했다. RED 검증은 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home ai character mapper preserves creator id for routing and character id for display" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home ai character route uses creator id and ignores invalid ids" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home ai character route creates creator channel intent"`에서 `HomeRecommendationAiCharacterRoute`, `toHomeRecommendationAiCharacterIntent`, `toHomeRecommendationAiCharacterRoute`, `HomeAiCharacterItem.creatorId` 미정의 컴파일 실패로 확인했다. 구현 후 동일 명령과 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`는 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-23: 사용자 추가 요청에 따라 `HomeAiCharacterItem.creatorId` 추가와 `크리에이터와 이야기를 나눠요!` item 터치 목적지를 캐릭터 상세에서 크리에이터 채널로 변경하는 후속 작업을 `Task 11.2`로 추가했다. DTO, UI model, mapper, `HomeMainFragment` 라우팅, mapper/intent 회귀 테스트, `creatorId <= 0L` no-op guard, `CharacterDetailActivity` 제거 항목을 구현 기준으로 명시했다. 문서 변경만 수행했으므로 Gradle 검증은 실행하지 않고 문서 재읽기로 확인한다. - 2026-06-06: 사용자 요청에 따라 `Phase 11: 방금 활동한 크리에이터 터치 액션 연결`을 추가하고 기존 `Phase 11: 최종 검증과 문서 기록`을 `Phase 12`로 뒤로 밀었다. 구현으로 최근 활동 카드 클릭 시 `Live`는 no-op, `LiveReplay`/`Audio`는 `AudioContentDetailActivity` + `Constants.EXTRA_AUDIO_CONTENT_ID = targetId`, `Community`는 `CreatorCommunityAllActivity` + `Constants.EXTRA_COMMUNITY_CREATOR_ID = targetId`로 이동하도록 연결했다. RED 검증은 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recent activity route maps activity type to target destination" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recent activity route ignores live unknown and missing target" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recent activity route creates expected intents"`에서 `HomeRecommendationRecentlyActiveCreatorRoute`, `toHomeRecommendationRecentlyActiveCreatorRoute()`, `toHomeRecommendationRecentlyActiveCreatorIntent()` 미정의 컴파일 실패로 확인했다. 구현 후 동일 명령은 BUILD SUCCESSFUL을 확인했다. - 2026-06-06: Task 11.1 회귀 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `ktlintCheck`에서는 기존 `.editorconfig`의 `disabled_rules` 속성 deprecation warning만 출력되었고 신규 실패는 없었다. 변경 파일 4개로 리뷰 게이트를 수행했고, `Live` no-op, `LiveReplay`/`Audio`/`Community` 목적지 및 extra 계약, invalid target no-op, Fragment callback 연결, Phase 11/12 문서 이동, 테스트 커버리지에 대해 blocking finding 없음으로 승인받았다. - 2026-06-05: Task 10.1 구현으로 홈 추천 배너 클릭 라우팅을 연결했다. `eventItem`은 `EventDetailActivity` + `Constants.EXTRA_EVENT`, `creatorId`는 `UserProfileActivity` + `Constants.EXTRA_USER_ID`, `seriesId`는 `SeriesDetailActivity` + `Constants.EXTRA_SERIES_ID`, `link`는 `Intent.ACTION_VIEW`로 실행한다. 여러 대상이 함께 내려오면 `eventItem` → `creatorId` → `seriesId` → `link` 순서로 우선 처리한다. RED 검증은 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route uses event creator series link priority"`에서 `HomeRecommendationBannerRoute`, `toHomeRecommendationBannerRoute()` 미정의 컴파일 실패로 확인했다. 구현 후 동일 테스트는 최초 `:app:kspDebugKotlin`의 `java.io.EOFException`으로 중단되었고, `--rerun-tasks` 재실행에서 BUILD SUCCESSFUL을 확인했다. 이후 내부 딥링크 판별 누락 지적에 따라 `BuildConfig.APPSCHEME` custom scheme과 `https://${BuildConfig.APPSCHEME}.onelink.me` app link만 내부 딥링크로 인정하고, `mailto:` 같은 외부 custom scheme은 이동하지 않도록 보강했다. RED 검증은 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route maps creator series web and internal deeplink" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route ignores blank malformed and scheme-less links"`에서 2개 테스트 실패로 확인했고, 수정 후 동일 명령은 BUILD SUCCESSFUL을 확인했다. - 2026-06-05: Task 10.1 최종 회귀 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `ktlintCheck`에서는 기존 `.editorconfig`의 `disabled_rules` 속성 deprecation warning만 출력되었고 신규 실패는 없었다. - 2026-06-05: Task 10.1 리뷰 반려 대응으로 라우팅 결정 테스트에 더해 실제 `Intent` 생성 계약 테스트를 추가했다. `HomeRecommendationBannerRoute.toHomeRecommendationBannerIntent(context)`를 추가해 `EventDetailActivity`/`UserProfileActivity`/`SeriesDetailActivity` component와 `Constants.EXTRA_EVENT`/`Constants.EXTRA_USER_ID`/`Constants.EXTRA_SERIES_ID` extra, `Intent.ACTION_VIEW` link data를 검증하도록 했다. 또한 `creatorId + seriesId + link`, `seriesId + link` 조합 우선순위 테스트를 추가했다. RED 검증은 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route creates activity and link intents with expected extras"`에서 `toHomeRecommendationBannerIntent()` 미정의 컴파일 실패로 확인했고, 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route creates activity and link intents with expected extras" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner route uses creator over series and series over link priority"`는 BUILD SUCCESSFUL을 확인했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-05: 사용자 추가 요청에 따라 배너 `link` 값이 내부 딥링크이면 기존 딥링크 실행 정책에 따라 앱 내부 딥링크를 실행하도록 `Task 10.1`과 확인 완료 사항에 반영했다. 문서 변경만 수행했으므로 Gradle 검증은 실행하지 않고 문서 재읽기로 확인한다. - 2026-06-05: 사용자 요청에 따라 `구현 중 확인 필요` 섹션을 `확인 완료 사항`으로 정리하고, 배너 `type` 제거 후 `eventItem`/`creatorId`/`seriesId`/웹 URL `link` 기준 이동 정책을 새 `Phase 10: 배너 이동 정책 연결`로 추가했다. 기존 `Phase 10: 최종 검증과 문서 기록`은 `Phase 11`로 변경하고 task 번호와 수동 확인 항목에 배너 목적지별 클릭 이동 확인을 반영했다. 문서 변경만 수행했으므로 Gradle 검증은 실행하지 않았고, `plan-task.md` 재읽기로 Phase 10/11 및 확인 완료 사항 반영을 확인했다. - 2026-06-05: 사용자 요청에 따라 Phase 9에 `Task 9.6: 홈 추천 프로필 이미지 원형 처리와 상대 시간 표시 적용`을 추가했다. 기존 `GetCommunityPostListResponse.relativeTimeText(context)`가 `dateUtc`를 UTC로 파싱해 디바이스 타임존 기준 상대 시간 문자열을 만들고, 홈 추천 mapper가 현재 `activityAt`/`createdAt` 원문을 그대로 전달하는 것을 확인해 공통 상대 시간 함수 분리와 최근 활동/인기 커뮤니티 재사용 항목으로 기록했다. 문서 변경만 수행했으므로 Gradle 검증은 실행하지 않고 문서 재읽기로 확인한다. - 2026-06-05: Task 9.6 구현으로 `RelativeTimeFormatter`를 추가해 UTC timestamp를 디바이스 기본 타임존 기준 상대 시간 문자열로 변환하고, 홈 추천 응답 매핑에서 `HomeActiveCreatorItem.activityAt`과 `HomePopularCommunityPostItem.createdAt`에 적용했다. 최근 활동/장르/응원 creator profile image는 `CircleCropTransformation` 기반 공통 helper로 원형 로딩하도록 변경했다. RED 검증은 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`에서 `homeCreatorProfileImageTransformations`, `toUiModel(context)` 미정의 컴파일 실패로 확인했고, 구현 후 동일 targeted test와 `./gradlew :app:compileDebugKotlin`은 BUILD SUCCESSFUL을 확인했다. `./gradlew :app:ktlintCheck`는 최종 diff에 없는 기존 `GetCommunityPostListResponse.kt`의 `creator_community` package-name 위반으로 실패해 별도 기존 이슈로 분리했다. - 2026-06-05: Task 9.6 후속 수정으로 `HomeRecommendationViewModel`에 `Context`를 주입해 보관하던 구조를 제거했다. `HomeRecommendationViewModel`은 다시 `HomeRecommendationViewModel(get())`로 Repository만 주입받고, UTC 상대 시간 문자열 변환은 `HomeMainFragment`의 UI 바인딩 경계에서 `requireContext()`로 처리하도록 이동했다. RED 검증은 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recommendation viewmodel does not keep android context"`에서 ViewModel source에 `android.content.Context`가 남아 실패하는 것으로 확인했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 BUILD SUCCESSFUL임을 확인했다. - 2026-06-02: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md`, `docs/agent-guides/work-plan-docs.md`를 확인해 계획 문서 위치와 phase/task 형식을 맞췄다. - 2026-06-02: `HomeMainFragment`, `fragment_v2_main_home.xml`, `HomeApi`, `HomeRepository`, `AppDI`, `ApiResponse`, 주요 v2 widget 파일을 확인해 신규 API/Repository/ViewModel과 UI 작업 범위를 계획에 반영했다. - 2026-06-02: 사용자 추가 제공 정보에 따라 `ViewPager2`/swipe 전환과 tab별 Fragment 선행 생성을 제외하고, `TextTabBarView` 아래 추천 content만 세로 스크롤되도록 Phase 5와 수동 확인 항목을 갱신했다. - 2026-06-02: Phase 1 파일과 PRD Section Mapping을 확인해 `HomeMainFragment`/`fragment_v2_main_home.xml`은 아직 빈 홈 v2 shell이고, Phase 1-3 범위에서는 UI/ViewModel/adapter를 만들지 않는 것으로 구현 경계를 고정했다. - 2026-06-02: `RecommendedActivityTypeTest`를 먼저 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest"`를 실행해 `model` 패키지, mapper 함수, `home_recommendation_activity_*` 리소스 미존재로 실패하는 RED 상태를 확인했다. - 2026-06-02: `HomeRecommendationApi`, `HomeRecommendationModels`, `HomeRecommendationRepository`를 추가하고 `AppDI.kt`의 `networkModule`/`repositoryModule`에 신규 API/Repository를 등록했다. Phase 4 대상인 `HomeRecommendationViewModel` 등록은 추가하지 않았다. - 2026-06-02: `RecommendedActivityType`, `HomeRecommendationMappers`, 한국어/영어/일본어 string resource를 추가한 뒤 targeted test를 재실행해 PASS로 GREEN 전환을 확인했다. - 2026-06-02: `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `lsp_diagnostics`는 `kotlin-lsp` 미설치로 실행되지 않아 Gradle compile/test/ktlint 결과로 보완했다. - 2026-06-02: 백엔드 activity type code가 대소문자와 무관하게 들어와도 매핑되도록 `RecommendedActivityTypeTest`에 `live`, `Live_RePlay` 케이스를 추가했다. 수정 전 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest"`에서 해당 2개 테스트 실패로 RED를 확인했다. - 2026-06-02: `RecommendedActivityType.from()`의 code 비교를 `equals(ignoreCase = true)`로 변경한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.RecommendedActivityTypeTest"`, `./gradlew :app:compileDebugKotlin`을 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: Phase 6까지 진행했을 때 실제 API/ViewModel 연동 전에도 UI 배치와 섹션 표시 형태를 확인할 수 있도록 샘플 데이터를 임시 주입하는 검증 방식을 Phase 6 설명과 섹션별 검증 항목에 추가했다. 샘플 데이터는 Phase 9 실제 상태 바인딩 전 제거하거나 대체해야 한다. - 2026-06-02: Phase 4.1로 `HomeRecommendationUiState`, `HomeRecommendationUiModels`, `HomeRecommendationResponse.toContent()`와 섹션별 mapper를 추가했다. DTO를 Fragment/ViewHolder 계약으로 직접 노출하지 않고, 첫 오디오 태그(`First`/`Point`/`Free`), AI character original title 표시 여부, 인기 커뮤니티 유료 상태 모델을 UI model로 변환하도록 구성했다. - 2026-06-02: Phase 4.2로 `HomeRecommendationViewModel`을 추가해 `recommendationStateLiveData`, `toastLiveData`, `isLoading`, `loadRecommendations()`, `followCreators(sectionKey, creatorIds)`를 구현했다. 추천 API 성공 시 Content/Empty 상태로 분기하고, 실패/data null/Throwable은 Error 상태와 unknown error toast를 노출하도록 했다. 빈 creatorIds에서는 모두 팔로우 API를 호출하지 않는다. - 2026-06-02: Phase 4.3으로 `AppDI.kt`에 `HomeRecommendationViewModel` import와 `viewModel { HomeRecommendationViewModel(get()) }` 등록을 추가했다. `lsp_diagnostics`는 `kotlin-lsp` 미설치로 실행되지 않아 Gradle 검증으로 보완할 예정이다. - 2026-06-02: Phase 4 검증으로 `./gradlew :app:compileDebugKotlin --rerun-tasks`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*"`, `./gradlew :app:testDebugUnitTest`, `./gradlew :app:ktlintCheck`, `./gradlew :app:compileDebugKotlin`을 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 최초 증분 컴파일에서 `recentContentModule`, `chatTalkRoomModule` unresolved가 발생했으나 해당 파일이 존재하고 tracked 상태임을 확인했으며, `--rerun-tasks` 재실행 후 성공해 증분 캐시 문제로 분리했다. ktlint 최초 실행에서는 `HomeRecommendationMappers.kt`의 긴 줄 2건이 실패해 줄바꿈 수정 후 재실행으로 성공했다. - 2026-06-02: Phase 5 RED 검증으로 `HomeMainFragmentLayoutTest`를 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`를 실행했다. 구현 전 `view_home_title_bar`, `text_tab_bar_home`, `nsv_home_recommendation_content`, 섹션/RecyclerView/title icon ID가 없어 컴파일 실패하는 RED 상태를 확인했다. - 2026-06-02: Phase 5.1로 `fragment_v2_main_home.xml`을 `ConstraintLayout` 기반으로 구성해 `view_title_bar_home`, `view_text_tab_bar`, `NestedScrollView` 추천 content shell을 배치했다. title bar와 tab bar는 scroll container 밖에 두고, 추천 content 내부에 섹션별 container와 가로 `RecyclerView`, 사업자 정보 영역만 추가했다. `ViewPager2`, `FragmentStateAdapter`, 랭킹/팔로잉 전용 content는 추가하지 않았다. - 2026-06-02: Phase 5.2로 `view_title_bar_home.xml`의 right icon을 `ic_bar_cash`, `ic_bar_search`, `ic_bar_bell` 순서로 확장하고, `HomeMainFragment`에서 `TextTabBarView` 메뉴를 추천/랭킹/팔로잉 순서와 selected index 0으로 바인딩했다. 좌우 swipe 전환은 추가하지 않았다. - 2026-06-02: Phase 5 GREEN/검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `lsp_diagnostics`는 `kotlin-lsp` 미설치로 실행되지 않아 Gradle compile/test/ktlint 결과로 보완했다. - 2026-06-02: 홈 상단 `TextTabBarView`가 추천 화면 전용이 아니라 홈 탭 공용 탭(추천/랭킹/팔로잉)임을 반영해 string resource 이름을 `home_recommendation_tab_*`에서 `screen_home_tab_*`으로 변경했다. 구현 전 `HomeMainFragment` 참조만 먼저 바꾼 `./gradlew :app:compileDebugKotlin`에서 새 string ID 미정의로 RED 실패를 확인했다. - 2026-06-02: 배너 아래 섹션 제목 요구사항을 반영해 `최근 데뷔한 크리에이터`, `처음부터 함께 성장!`, `크리에이터와 이야기를 나눠요!`에는 `view_section_title`의 더보기 chevron을 표시하고, `방금 활동한 크리에이터`, `최근 응원이 많은 크리에이터`, `인기 커뮤니티`에는 chevron을 숨기도록 `HomeMainFragment`에서 초기화했다. 장르 섹션은 백엔드 장르명 바인딩을 위해 `view_section_title`을 쓰지 않고 별도 title row로 분리했으며 장르명 색상은 `@color/color_3bb9f1`로 지정했다. 구현 전 `HomeMainFragmentLayoutTest`에서 새 section title ID 미정의로 RED 실패를 확인했고, 이후 `./gradlew :app:compileDebugKotlin --rerun-tasks`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --rerun-tasks`, `./gradlew :app:mergeDebugResources`가 BUILD SUCCESSFUL임을 확인했다. 최초 병렬 Gradle 실행 중 KSP incremental cache 오류가 있었으나 순차 재실행으로 성공해 캐시 경합 문제로 분리했다. - 2026-06-02: Phase 6.1-6.4로 라이브/배너/최근 활동/최근 데뷔/첫 오디오/AI 캐릭터/장르 크리에이터/응원 크리에이터 adapter와 binder를 추가하고, `HomeMainFragment`에서 빈 리스트 섹션을 `GONE` 처리하도록 바인딩했다. 배너는 `BannerView`, 라이브는 `LiveThumbnailSimpleView`, 첫 오디오는 `AudioContentCardView`, AI 캐릭터는 `CharacterChatThumbnailView`를 재사용했고, 임시 샘플 데이터는 `phase6SampleContent()`에 격리해 Phase 9에서 실제 상태 바인딩으로 교체할 수 있게 했다. 모두 팔로우 버튼은 creatorId가 비어 있으면 callback을 호출하지 않도록 연결했다. - 2026-06-02: Phase 6 검증으로 `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest --tests kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`, `./gradlew :app:installDebug`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 최초 병렬 Gradle 실행은 Kotlin 출력 디렉터리 잠금 경합으로 실패해 `./gradlew --stop` 후 순차 재실행했다. `lsp_diagnostics`는 `kotlin-lsp` 미설치와 XML LSP 미구성으로 실행되지 않았다. 연결 기기에서 런처 실행은 성공했지만 `MainV2Activity` 직접 실행은 `exported=false`로 차단되었고, 런처 경로는 splash의 검은 화면에 머물러 홈 추천 UI 실기기 육안 확인은 완료하지 못했다. - 2026-06-02: Phase 6 리뷰에서 Phase 7 전 빈 `popularCommunityPosts` 상태에도 `인기 커뮤니티` 섹션 container가 기본 노출될 수 있음을 확인했다. `HomeMainFragmentLayoutTest`에 `popular community section is hidden until phase7 binding is implemented` 회귀 테스트를 추가해 수정 전 실패(AssertionError)를 확인했고, `fragment_v2_main_home.xml` 기본 visibility와 `HomeMainFragment.bindPopularCommunitySection()`을 추가한 뒤 동일 테스트가 BUILD SUCCESSFUL로 통과함을 확인했다. - 2026-06-02: Phase 6 수정 후 `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다. 재리뷰에서 이전 `인기 커뮤니티` 빈 섹션 노출 지적이 수정됐고, Phase 6.1-6.4 범위의 남은 승인 차단 이슈가 없다는 무조건 승인을 받았다. - 2026-06-02: Figma `24:5529` 기준 `방금 활동한 크리에이터` 섹션을 확인해 기존 세로 프로필형 카드가 디자인과 다름을 확인했다. `HomeMainFragmentLayoutTest`에 `recent activity creator item matches figma capsule dimensions` 테스트를 먼저 추가했고, 기존 구현에서 실패하는 RED 상태를 확인했다. 이후 `item_home_recent_activity_creator.xml`을 `244dp x 76dp` 가로 capsule 구조로 변경하고 `HomePhase6Adapters.kt`에서 보조 텍스트를 `activityAt`으로 바인딩하도록 수정했으며, 동일 테스트가 BUILD SUCCESSFUL로 통과함을 확인했다. - 2026-06-02: `HomePhase6Adapters.kt`에 모든 섹션 adapter/binder가 모여 있어 섹션별 유지보수가 어렵다는 지적에 따라 adapter/binder를 섹션 단위 파일로 분리했다. `HomePhase6Adapters.kt`는 삭제하고, 공통 item margin 생성 로직은 `HomeRecyclerItemLayoutParams.kt`로 분리했다. 동작 변경 없이 파일 구조만 정리하는 리팩터링이다. - 2026-06-02: Figma `24:5516` 기준 `ll_home_live_section`을 확인해 라이브 행 높이, `전체` item, 아이템 간격이 디자인과 다름을 확인했다. `HomeMainFragmentLayoutTest`에 live row 치수, live adapter item gap, 20개 초과 시 `전체` item 추가 회귀 테스트를 먼저 추가했고, 구현 전 adapter `전체` view type 미지원과 20개 cap 미지원으로 RED 실패를 확인했다. 이후 `fragment_v2_main_home.xml`에서 별도 `tv_home_live_more` overlay를 제거하고, `HomeLiveAdapter`가 live를 최대 20개까지 표시한 뒤 20개 초과 시 마지막 item으로 `58dp x 102dp` 검은 배경 `전체`를 추가하도록 수정했으며, 동일 테스트가 BUILD SUCCESSFUL로 통과함을 확인했다. - 2026-06-02: 라이브 섹션 `전체` item 방식 변경 후 `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `lsp_diagnostics`는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. - 2026-06-02: Figma `24:5534` 기준 `최근 데뷔한 크리에이터` 섹션을 확인해 기존 `112dp` 세로 원형 profile item이 디자인과 다름을 확인했다. `HomeMainFragmentLayoutTest`에 recent debut 카드 치수와 목록 간격 테스트를 먼저 추가했고, 기존 구현에서 `recent debut creator item matches figma card dimensions`, `home recent debut section matches figma list spacing` 두 테스트가 실패하는 RED 상태를 확인했다. - 2026-06-02: `item_home_recent_debut_creator.xml`을 `205dp x 259dp` 이미지 카드 구조로 변경하고, radius `14dp` 배경/하단 dim gradient/`24sp` bold 중앙 닉네임을 적용했다. `HomeRecentDebutCreatorAdapter`에는 최근 데뷔 전용 `4dp` item gap을 적용했고, `rv_home_recent_debut_creators` 시작 padding을 Figma 기준 `14dp`로 맞췄다. 동일 targeted 테스트 재실행 결과 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: 최근 데뷔 수정 후 `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`는 BUILD SUCCESSFUL을 확인했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`는 이번 최근 데뷔 테스트는 통과했지만 기존 `home live section matches figma row dimensions`가 `rv_home_lives` 높이 기대값(`102dp`)과 현재 XML `wrap_content`가 맞지 않아 실패했다. 이후 사용자 확인에 따라 `rv_home_lives` 높이는 아이템 높이를 따라가야 하므로 `wrap_content`가 올바른 계약으로 정정했다. - 2026-06-29: 사용자 추가 요청에 따라 Figma `24:5534`, `24:5537` 기준 최근 데뷔 item의 전체 rounded clipping과 축소 치수를 보완했다. `HomeMainFragmentLayoutTest`의 recent debut 테스트를 `185dp x 234dp`, root `clipToOutline`, image `match_parent` 기대값으로 먼저 변경했고, 기존 `205dp x 259dp` 구현에서 `HomeMainFragmentLayoutTest.kt:232` assertion 실패로 RED를 확인했다. - 2026-06-29: GREEN 구현으로 `item_home_recent_debut_creator.xml` root를 `185dp x 234dp`로 축소하고 image를 `match_parent`로 변경했다. 저장소 clipping 패턴에 맞춰 `HomeRecentDebutCreatorAdapter` ViewHolder에서 item root에 `clipToOutline=true`와 `ViewOutlineProvider.setRoundRect(..., radius_14)`를 적용해 image, dim gradient, nickname 전체가 같은 radius로 잘리게 했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.recent debut creator item matches figma card dimensions" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recent debut section matches figma list spacing"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck`가 모두 BUILD SUCCESSFUL임을 확인했다. `lsp_diagnostics`는 Kotlin/XML LSP 도구가 이 환경에 제공되지 않아 Gradle compile/test/ktlint로 보완했다. - 2026-06-29: 리뷰에서 adapter가 새 `RecyclerView.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)`를 만들어 XML root의 `185dp x 234dp` 치수 계약을 실제 RecyclerView 경로에서 약화할 수 있다는 차단 이슈를 확인했다. `HomeRecentDebutCreatorAdapter`가 inflate된 root layout params를 `RecyclerView.LayoutParams(existing)`로 보존하고 `marginEnd`만 추가하도록 수정했으며, `HomeMainFragmentLayoutTest`의 adapter 경로 검증에 `185dp x 234dp` assertion을 추가했다. 재검증으로 targeted recent debut tests, 전체 `HomeMainFragmentLayoutTest`, `./gradlew :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck`가 모두 BUILD SUCCESSFUL임을 확인했다. 보안 리뷰가 지적한 `app/build.gradle release debuggable true`는 현재 워크트리에 존재하는 이번 작업 범위 밖 변경으로, 사용자 승인 없이 수정하지 않았다. - 2026-06-02: Figma `24:5539` 기준 `처음부터 함께 성장!` 섹션을 확인해 기존 `AudioContentCardView` 기반 item이 전용 profile row 구조와 item gap을 재현하기 어렵다는 점을 확인했다. `HomeMainFragmentLayoutTest`에 first audio 전용 item 치수, 목록 간격, tag visibility 테스트를 먼저 추가했고, 구현 전 `item_home_first_audio_content`와 관련 view ID 미존재로 RED 컴파일 실패를 확인했다. - 2026-06-02: `item_home_first_audio_content.xml`을 추가하고 `HomeFirstAudioAdapter`가 `AudioContentCardView` 대신 전용 item을 inflate하도록 변경했다. 썸네일은 `185dp x 185dp`, creator profile은 `42dp x 42dp`, item gap은 `4dp`로 맞췄고, `First`/`Point`/`Free` tag visibility는 기존 `tags` 모델로 바인딩했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"` 재실행 결과 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: 리뷰에서 first audio 썸네일 이미지 clipping과 `Free` tag visibility 테스트 누락이 차단 이슈로 지적되어 `item_home_first_audio_content.xml`에 `clipToOutline="true"`를 추가하고 `HomeMainFragmentLayoutTest`에서 `First`/`Point`/`Free` 개별 tag visibility를 모두 검증하도록 보강했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: first audio adapter에서 nullable image URL이 재사용된 ViewHolder의 이전 이미지를 남길 수 있는 케이스를 확인했다. `first audio adapter clears nullable images` 테스트를 추가해 수정 전 실패를 확인한 뒤, `HomeFirstAudioAdapter`에서 `coverImage`/`creatorProfileImage`가 null이면 drawable을 명시적으로 비우도록 수정했다. 이후 해당 테스트, `HomeMainFragmentLayoutTest` 전체, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: first audio 썸네일은 이미지뿐 아니라 `FIRST`/`Point`/`Free` overlay까지 같은 radius로 잘려야 한다는 사용자 확인에 따라 Coil transformation 방식이 아니라 썸네일 parent `FrameLayout` 전체에 Kotlin `clipToOutline = true`와 `ViewOutlineProvider.setRoundRect(..., 14dp)`를 적용했다. XML `android:clipToOutline`은 lint 경고를 피하기 위해 사용하지 않았다. `first audio adapter clips thumbnail container` 테스트를 추가했고, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행해 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: `AudioContentCardView`의 이미지 표시 영역도 first audio 전용 item과 동일하게 이미지와 overlay tag가 함께 roundRect clipping되도록 확인하고, `First`/`Point`/`Free` tag 속성을 `item_home_first_audio_content.xml`과 맞췄다. `HomeMainFragmentLayoutTest`에 `audio content card clips image area and overlay tags together`, `audio content card tag attributes match first audio item` 회귀 테스트를 추가했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: `AudioContentCardView`와 `item_home_first_audio_content.xml`의 badge별 제어를 위해 `Original`/`First`/`Point`/`Free` badge에 안정적인 id를 추가했다. `AudioContentCardView`의 runtime 생성 badge에도 동일 id를 부여하고, `First`와 `Free` badge 높이를 `24dp`로 유지하도록 맞췄다. `HomeMainFragmentLayoutTest`에서 badge id/높이 계약을 검증하도록 보강했으며 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행해 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-02: Figma `24:5551` 기준 `크리에이터와 이야기를 나눠요!` 섹션을 확인해 기존 목록 padding `20dp`, adapter item gap `12dp`, generic `RecyclerView.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)` 적용이 `view_character_chat_thumbnail.xml`의 root `185dp x wrap_content` 계약을 보존하지 못함을 확인했다. `HomeMainFragmentLayoutTest`에 character thumbnail root 치수, AI 캐릭터 목록 spacing, adapter item dimension 회귀 테스트를 먼저 추가했고, 수정 전 targeted test에서 새 테스트 2건 실패로 RED를 확인했다. - 2026-06-02: `rv_home_ai_characters` padding을 `14dp`로 맞추고, `HomeAiCharacterAdapter`에서 inflate된 `view_character_chat_thumbnail.xml` root layout params를 유지한 채 `marginEnd`만 `4dp`로 적용했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `lsp_diagnostics`는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. - 2026-07-12 현재 정책: 아래 2026-06-04 장르 크리에이터 UI/모두 팔로우/grid/snapping 관련 검증 기록은 과거 구현 이력으로만 유지한다. Phase 14 이후 `genreCreators` 섹션은 추천 탭에 표시하지 않고, follow-all API 호출과 완료 상태 갱신도 사용하지 않는다. - 2026-06-04: Figma `24:5611`, 완료 버튼 `24:9092` 기준 장르 크리에이터 모두 팔로우 버튼을 확인해 기본 상태는 `ic_new_follow`, 투명 배경과 흰색 30% stroke, 흰색 텍스트이고 완료 상태는 `ic_new_following`, 흰색 배경, 검은색 텍스트여야 함을 확인했다. `HomeMainFragmentLayoutTest`에 모두 팔로우 기본/완료 상태와 완료 상태 no-op 회귀 테스트를 먼저 추가했고, 구현 전 `bg_home_follow_all_button` 리소스 미존재로 RED 컴파일 실패를 확인했다. - 2026-06-04: `bg_home_follow_all_button.xml`을 추가하고 `view_home_follow_all_button.xml`의 기본 상태를 Figma 활성 상태로 변경했다. `HomeFollowAllButtonBinder`는 완료 상태에서 `bg_round_corner_999_white`, 검은 텍스트, `ic_new_following`, `모두 팔로우 완료`를 적용하고 터치 시 callback을 호출하지 않도록 수정했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `./gradlew :app:installDebug`는 연결된 기기가 없어 `No connected devices!`로 실패해 실기기 설치 확인은 수행하지 못했다. - 2026-06-04: 사용자 추가 요청에 따라 Figma `24:5611` 기준 `장르의 크리에이터` 섹션을 그룹 단위 snapping card 구조로 수정했다. 구현 전 `HomeMainFragmentLayoutTest`에 genre group card/profile/snapping/filtering/follow callback 회귀 테스트를 추가했고, 기존 구현에서 `item_home_genre_creator_group`, `bg_home_genre_creator_group`, `submitGroups` 등 새 계약 미존재로 RED 컴파일 실패를 확인했다. 이후 `HomeGenreCreatorAdapter`를 group page adapter로 변경하고, `HomePagerSnapRecyclerView`, `item_home_genre_creator_group.xml`, `item_home_genre_creator_profile.xml`, `bg_home_genre_creator_group.xml`을 추가해 각 page에 장르 제목, 8명 grid, 모두 팔로우 버튼, dark-blue gradient, `PagerSnapHelper` snapping을 적용했다. targeted 테스트 최초 재실행에서는 null image가 Coil `ImageLoaderProvider`를 요구해 실패했고, nullable image는 drawable을 명시적으로 비우도록 수정한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`가 BUILD SUCCESSFUL임을 확인했다. 추가 검증으로 `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. ktlint 최초 실행은 테스트 파일 긴 줄 2곳으로 실패했고 줄바꿈 수정 후 재실행으로 성공했다. `lsp_diagnostics`는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. 실기기 수동 확인은 연결 기기가 없어 수행하지 못했다. - 2026-06-04: 사용자 추가 요청에 따라 Figma `24:5636` 기준 `최근 응원이 많은 크리에이터` 섹션을 card 내부 profile grid 구조로 수정했다. 구현 전 `HomeMainFragmentLayoutTest`에 cheer card/grid/width/follow callback 회귀 테스트를 추가했고, 기존 구현에서 `item_home_cheer_creator_group`, `bg_home_cheer_creator_group`, `HomeCheerCreatorAdapter(onFollowAllClick)`, `submitSection` 등 새 계약 미존재로 RED 컴파일 실패를 확인했다. 이후 `HomeCheerCreatorAdapter`를 단일 card adapter로 변경하고, `item_home_cheer_creator_group.xml`, `bg_home_cheer_creator_group.xml`을 추가해 `14dp` horizontal padding 기반 full-width card, 최대 8명 4열 grid, card 내부 모두 팔로우 버튼을 적용했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`, `./gradlew :app:installDebug`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`로 설치된 debug 앱 실행까지 확인했다. 리뷰 게이트에서 무조건 승인도 확인했다. `lsp_diagnostics`는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. - 2026-06-04: 리뷰에서 모든 genre group의 creators가 비어 있을 때 adapter는 빈 목록이 되지만 `ll_home_genre_creator_section`이 `VISIBLE`로 남을 수 있다는 차단 이슈를 확인했다. `HomeMainFragmentLayoutTest`에 `visibleHomeGenreCreatorGroups()` all-empty 회귀 테스트를 추가했고, helper 미구현으로 RED 컴파일 실패를 확인했다. 이후 `HomeRecommendationUiModels.kt`에 visible group helper를 추가하고 `HomeMainFragment.bindGenreCreatorSection()`의 visibility와 submit 기준을 같은 filtered/capped 목록으로 맞췄으며, adapter 자체의 empty/max 5 방어도 유지했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 장르 크리에이터 group card 폭을 고정 `374dp`가 아니라 현재 화면 가로 크기에서 좌우 여백 `28dp`를 뺀 값으로 계산하도록 수정했다. 구현 전 `HomeMainFragmentLayoutTest`의 group layout 계약을 `match_parent`로 바꾸고 adapter item width가 parent width `402dp - 28dp = 374dp`가 되는 회귀 테스트를 추가했으며, 기존 구현에서 XML `374dp` 고정 및 adapter 폭 미계산으로 RED 실패를 확인했다. 이후 `item_home_genre_creator_group.xml` root width를 `match_parent`로 바꾸고 `HomeGenreCreatorAdapter`에서 parent width 기반 `RecyclerView.LayoutParams`를 설정하도록 변경했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 작은 해상도에서 장르 크리에이터 grid의 profile item/image `75dp` 고정값 때문에 4열 끝부분이 잘릴 수 있어, group card 내부 grid 가용 폭에 맞춰 profile cell 크기를 계산하도록 수정했다. 구현 전 `HomeMainFragmentLayoutTest`에 작은 parent width `360dp` 기준 cell size가 `(360 - 28 - 28 - 14*3) / 4`로 계산되는 회귀 테스트를 추가했고, 기존 `75dp` 고정 구현에서 RED 실패를 확인했다. 이후 `item_home_genre_creator_group.xml`의 grid width를 `match_parent`로 변경하고, `HomeGenreCreatorAdapter`에서 card width, padding, column gap을 기준으로 profile item/image width/height를 동적으로 설정하도록 수정했다. `home_genre_creator_profile_size` dimen은 더 이상 사용하지 않아 제거했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: `item_home_genre_creator_profile.xml` 자체에도 profile root와 image가 `75dp`로 고정되어 있어 XML 계약을 `ConstraintLayout` 기반의 가변 cell + 1:1 image ratio로 변경했다. 구현 전 `HomeMainFragmentLayoutTest`의 profile XML 테스트를 root `match_parent`, image `0dp x 0dp`, `layout_constraintDimensionRatio="1:1"` 계약으로 바꿨고 기존 `LinearLayout`/`75dp` XML에서 RED 실패를 확인했다. 이후 root를 `ConstraintLayout`으로 전환하고 image는 parent width를 따르는 1:1 ratio로, nickname은 image 하단에 constraint 되도록 수정했다. adapter에서는 image width/height 직접 덮어쓰기를 제거하고 root cell width만 grid layout params로 설정하게 정리했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 실제 화면에서 장르 크리에이터 grid 우측 끝 이미지가 절반 정도 잘리는 문제를 수정했다. 원인은 profile size 계산 시 group item width가 아직 `MATCH_PARENT`이거나 미측정일 때 display width fallback을 사용해 cell이 과대 계산될 수 있는 점이었다. 구현 전 `HomeMainFragmentLayoutTest`에 group width가 미확정인 상황에서도 parent RecyclerView width `360dp` 기준으로 cell size가 계산되는 회귀 테스트를 추가했고, 기존 display width fallback에서 RED 실패를 확인했다. 이후 `HomeGenreCreatorAdapter.GenreCreatorGroupViewHolder`가 parent RecyclerView를 보관하고, card width가 확정되지 않은 경우 `parent.width - 28dp`를 사용해 profile cell 크기를 계산하도록 수정했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 한국어/영어 장르 suffix의 선행 공백이 실제 기기에서 보이지 않고, 우측 끝 profile image가 일부 잘리는 문제를 추가 수정했다. 일본어에는 공백을 넣으면 안 되므로 title row margin 방식은 사용하지 않고 한국어/영어 string에 `\u0020`를 사용해 선행 공백을 보존했다. `xml:space="preserve"` 시도는 Android resource merge에서 실패해 제거했고, stale incremental resource 산출물(`app/build/intermediates/incremental/debug/packageDebugResources`, `mergeDebugResources`)을 삭제한 뒤 재검증했다. profile cell 크기는 itemView 측정 폭이 parent RecyclerView content 폭보다 크게 잡히는 경우를 방지하도록 parent padding을 제외한 content width로 cap 했다. 구현 전 `HomeMainFragmentLayoutTest`에 parent padding이 있는 상태에서 measured group width가 과대 측정되어도 cell size가 `RecyclerView content width` 기준으로 계산되는 회귀 테스트와 한국어/영어는 선행 공백, 일본어는 선행 공백 없음 테스트를 추가해 RED 실패를 확인했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-04: 우측 profile image 잘림 범위가 group card/RecyclerView가 아니라 `item_home_genre_creator_group.xml` 내부 `GridLayout`임을 확인해, profile cell 계산 기준을 `GridLayout`의 실제 측정 width 우선으로 수정했다. 구현 전 `HomeMainFragmentLayoutTest`에 GridLayout width가 `300dp`로 측정된 경우 cell size가 `(300 - 14*3) / 4`로 계산되어야 하는 회귀 테스트를 추가했고, 기존 card width 기준 계산에서 RED 실패를 확인했다. 이후 `HomeGenreCreatorAdapter`의 `calculateProfileSize()`가 `creatorGrid.width`를 우선 사용하고, layout 이후 width가 확정되는 경우 `creatorGrid.post { ... }`에서 다시 바인딩해 재계산하도록 변경했다. 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-05: Figma `24:5645` 기준 Phase 7 인기 커뮤니티 섹션을 구현했다. 구현 전 `FeedViewTest`와 `HomeMainFragmentLayoutTest`에 기존 Community keyword 기본 노출, 추천용 keyword 숨김, 이미지 null 시 이미지 영역 `GONE`, 유료 미구매 overlay/가격 capsule, 구매 완료 overlay 숨김, 인기 커뮤니티 최대 10개 노출 테스트를 추가했고, 기존 구현에서 새 필드/뷰/adapter/helper 미존재로 RED 컴파일 실패를 확인했다. 이후 `FeedItem.Community`에 `imageUrl`, `price`, `existOrdered`, `showKeyword` 기본값 필드를 추가하고, `FeedCommunityView`/`FeedAdapter`가 커뮤니티 primary image와 유료 overlay를 지원하도록 확장했으며, `HomePopularCommunityAdapter`와 `HomeMainFragment` 바인딩을 추가했다. `popularCommunityPosts`는 최대 10개만 표시한다. - 2026-06-05: Phase 7 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedViewTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 최초 병렬 Gradle 실행은 KSP cache 경합으로 실패해 `./gradlew --stop` 후 순차 재실행했다. ktlint 최초 실행은 새 긴 줄 위반으로 실패했고 줄바꿈 수정 후 재실행으로 성공했다. `lsp_diagnostics`는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. - 2026-06-05: Phase 7 리뷰 게이트에서 `audioUrl` 상세 이동 데이터 유실과 유료 미구매 커뮤니티 원본 이미지 로드 가능성이 차단 이슈로 지적됐다. `HomeMainFragmentLayoutTest`에 `popular community mapper preserves audio url for detail data`, `home popular community adapter does not load original image for locked paid post` 회귀 테스트를 추가했고, 구현 전 `audioUrl` 필드 미존재로 RED 컴파일 실패를 확인했다. 이후 `FeedItem.Community.audioUrl` 기본값 필드와 mapper 전달을 추가하고, `HomePopularCommunityAdapter`가 `price > 0 && existOrdered == false`인 item의 원본 `imageUrl`을 로드하지 않도록 수정했다. - 2026-06-05: Phase 7 차단 이슈 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-05: 보안 재리뷰에서 유료 미구매 item으로 재바인딩할 때 기존 Coil 비동기 요청이 늦게 완료될 가능성이 차단 이슈로 지적됐다. `HomePopularCommunityAdapter.bindImage()`의 null/blank 경로에서 `imageView.dispose()`로 기존 Coil 요청을 취소한 뒤 drawable을 비우도록 보완했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-05: Phase 7 인기 커뮤니티 UI를 실제 기기에서 확인할 수 있도록 `HomeMainFragment.phase6SampleContent()`의 `popularCommunityPosts`에 무료/유료 미구매/구매 완료 샘플 3건을 임시 주입했다. 샘플은 추천용 계약에 맞춰 keyword를 숨기고, 유료 미구매 샘플은 lock overlay와 가격 capsule을 확인할 수 있게 구성했다. `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:ktlintCheck`, `./gradlew :app:mergeDebugResources`는 모두 BUILD SUCCESSFUL을 확인했다. `./gradlew :app:installDebug`는 연결 기기가 없어 `No connected devices!`로 실패해 실기기 설치/육안 확인은 수행하지 못했다. - 2026-06-05: 사용자 확인 중 커뮤니티 게시물 이미지가 잘리는 문제가 있어 `view_feed_community.xml`의 root `374dp`, 이미지 container `346dp x 236dp` 고정 크기를 제거했다. root와 이미지 container는 `match_parent`로 두고, `FeedCommunityView.onMeasure()`에서 실제 card content width 기준 Figma `346:236` 비율로 이미지 높이를 계산하도록 변경했다. Kotlin `clipToOutline`/`ViewOutlineProvider.setRoundRect(...)` 계약은 유지했다. `FeedViewTest`에 동적 이미지 폭/높이 회귀 테스트를 추가했으며, 최초 테스트는 이미지 URL 미바인딩으로 container가 `GONE`이라 실패했고 테스트 조건을 실제 표시 상태로 수정한 뒤 성공했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedViewTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`가 모두 BUILD SUCCESSFUL임을 확인했다. - 2026-06-05: Phase 8 사업자 정보 UI 구현으로 `fragment_v2_main_home.xml`에 기본 `maxLines=3`/`ellipsize=end`와 `tv_home_business_info_toggle`을 추가하고, `HomeBusinessInfoBinder` 및 `HomeMainFragment.setUpBusinessInfo()`에서 더보기/접기 토글과 `TextView.post { lineCount }` 기반 3줄 이하 toggle 숨김 처리를 연결했다. RED 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`를 먼저 실행해 `HomeBusinessInfoBinder`, `tv_home_business_info_toggle` 미존재 컴파일 실패를 확인했고, 구현 후 동일 targeted test와 `./gradlew :app:mergeDebugResources`가 BUILD SUCCESSFUL임을 확인했다. - 2026-06-05: 사용자 확인에 따라 `home_recommendation_business_info`와 기존 `company_info` 중복을 제거했다. `fragment_v2_main_home.xml`은 공용 `@string/company_info`를 참조하도록 변경하고, 한국어/영어/일본어 `home_recommendation_business_info` 리소스는 삭제했다. 리뷰 게이트에서 지적된 접힘 상태 lineCount 측정 문제는 전체 줄 수 측정 시 일시적으로 `maxLines=Int.MAX_VALUE`/`ellipsize=null`로 측정한 뒤 다시 접힘 상태로 되돌리도록 보완했고, 4줄 이상일 때 toggle이 노출되는 회귀 테스트를 추가했다. - 2026-06-05: `HomeBusinessInfoBinder`의 const naming warning을 해결하기 위해 `CollapsedMaxLines`를 `COLLAPSED_MAX_LINES`로 변경했다. 또한 긴 텍스트 줄 수 측정은 접힘 상태의 기존 layout을 읽지 않도록 `HomeMainFragment`에서 전체 줄 표시 상태를 적용한 뒤 다음 `post`에서 `lineCount`를 읽고 다시 접힘 상태로 되돌리도록 보완했다. - 2026-06-05: 사용자 추가 요청에 따라 사업자 정보 `더보기/접기`를 별도 하단 버튼이 아니라 본문 말줄임표 우측에 붙는 inline text action으로 변경하는 후속 작업을 진행한다. - 2026-06-05: 사업자 정보 `더보기/접기`를 하단 별도 버튼에서 본문 inline action으로 변경했다. `tv_home_business_info_toggle` View를 제거하고 `HomeBusinessInfoBinder`에서 접힘 상태는 `… 더보기`, 펼침 상태는 본문 뒤 `접기`를 clickable span으로 붙이도록 구현했다. `HomeMainFragmentLayoutTest`에 별도 toggle View 제거, inline 더보기 suffix, clickable span 확장/접기, 3줄 이하 action 미노출 테스트를 추가했다. 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-05: 사업자 정보 inline `더보기`가 말줄임표와 너무 붙어 보여 `… 더보기`에서 `… 더보기`로 간격을 한 칸 늘렸다. 관련 inline suffix 테스트 기대값도 함께 갱신한다. - 2026-06-05: 펼침 상태의 `접기` action도 본문에 붙어 보이지 않도록 action 앞에 공백 2칸을 추가했다. 접힘 상태의 `더보기` 간격과 동일한 시각적 여백을 유지하도록 테스트 기대값을 갱신했다. - 2026-06-05: Phase 9 RED 검증으로 `HomeMainFragmentLayoutTest`에 `home main fragment phase9 replaces sample content with viewmodel state binding`, `home main fragment phase9 connects loading toast and follow all callbacks` 소스 계약 테스트를 추가했다. 최초 실행은 테스트 source 경로 기준 문제로 `FileNotFoundException`이 발생해 경로를 보정했고, 이후 `phase6SampleContent`가 남아 있어 `AssertionError`로 RED 실패하는 것을 확인했다. - 2026-06-05: Phase 9.1로 `HomeMainFragment`에서 임시 `phase6SampleContent()`와 sample helper를 제거하고 `HomeRecommendationViewModel`을 주입해 `recommendationStateLiveData`, `isLoading`, `toastLiveData` observe 및 `loadRecommendations()` 호출을 연결했다. `Content`는 섹션 bind, `Empty`/`Error`는 빈 content bind로 섹션을 숨기고, loading은 `LoadingDialog`, toast는 `BaseFragment.showToast()`로 처리한다. - 2026-06-05: Phase 9.2로 live/banner/popular community callback을 Fragment에 연결하고, 기존 라우팅 extra가 확인된 creator/audio/AI/community만 최소 Intent 이동으로 연결했다. 목적지가 확정되지 않은 live/banner는 adapter callback을 Fragment로 위임하되 no-op으로 유지했다. `HomeRecentActivityCreatorAdapter`, `HomeRecentDebutCreatorAdapter`, `HomeFirstAudioAdapter`, `HomeAiCharacterAdapter`, `HomeGenreCreatorAdapter`, `HomeCheerCreatorAdapter`에는 기존 호출부 영향이 없도록 기본값 있는 click callback을 추가했다. - 2026-06-05: Phase 9 GREEN/검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home main fragment phase9*"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`, `./gradlew :app:testDebugUnitTest`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 최초 병렬 `compileDebugKotlin`은 Kotlin output directory 경합으로 실패했으나 순차 재실행에서 성공해 캐시/경합 문제로 분리했다. `lsp_diagnostics`는 Kotlin/XML LSP 미구성으로 실행하지 못해 Gradle compile/test/ktlint로 보완했다. - 2026-06-05: Phase 9.3으로 변경된 홈 추천 응답 스키마를 반영했다. `HomeLiveItem`은 `roomId`, `creatorNickname`, `creatorProfileImage`만 사용하고, `HomeBannerItem`은 `imageUrl`, `eventItem`, `creatorId`, `seriesId`, `link`로 매핑한다. `HomeActiveCreatorItem`은 `creatorNickname`, `creatorProfileImage`, `activityType`, `activityAt`, `targetId`로 매핑하고 응답에서 제거된 `creatorId` 기반 최근 활동 프로필 이동은 제거했다. `HomeCreatorItem`의 `creatorNickname`/`creatorProfileImage`, `HomeFirstAudioContentItem`의 `releaseDate` 제거, `HomeGenreCreatorGroupItem.genreName`도 mapper/UI model에 반영했다. 실제 UI에서 쓰지 않거나 응답에서 제공하지 않는 nullable placeholder 값은 추가하지 않았다. - 2026-06-05: Phase 9.3 RED 검증으로 `HomeMainFragmentLayoutTest`에 `home recommendation mapper uses changed response fields`를 추가하고 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recommendation mapper uses changed response fields"`를 실행했다. 구현 전 `HomeRecommendationMappers.kt`가 제거된 `liveId`, `creatorId`, `imageUrl`, `title`, `beginDateTime`, `bannerId`, `type`, `linkUrl`, `targetId`, `releaseDate`, `genre` 필드를 참조해 `:app:compileDebugKotlin` 컴파일 실패하는 RED 상태를 확인했다. mapper/UI model/adapter 수정 후 동일 테스트를 재실행해 BUILD SUCCESSFUL을 확인했다. - 2026-06-05: Phase 9.3 회귀 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 전체 홈 테스트 최초 실행에서는 non-null profile image helper가 Coil `ImageLoaderProvider` 초기화를 요구해 실패했고, 빈 문자열 이미지를 이미지 없음으로 처리하도록 `HomeFirstAudioAdapter`, `HomeGenreCreatorAdapter`, `HomeCheerCreatorAdapter`를 보완한 뒤 재실행해 성공했다. `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐고 lint 실패는 없었다. - 2026-06-05: Phase 9.3 리뷰에서 `HomeBannerBinder`가 `imageUrl`을 click source key로 쓰면 중복 이미지 URL 배너에서 첫 번째 항목으로 잘못 매칭될 수 있다는 차단 이슈를 확인했다. `HomeBannerBinder`를 position 기반 synthetic id 매칭으로 수정하고, `HomeMainFragmentLayoutTest`에 `home banner binder matches duplicate image banners by position` 회귀 테스트를 추가했다. - 2026-06-05: Phase 9.3 리뷰 지적 수정 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home banner binder matches duplicate image banners by position"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다. 배너 회귀 테스트 최초 실행은 `HomeRecommendationBannerSection`, `HomeRecommendationBannerUiModel` import 누락으로 컴파일 실패했고, import 보정 후 성공했다. - 2026-06-05: Phase 9.4로 홈 추천 API 응답 바인딩 후 `java.io.IOException: unexpected journal header: [libcore.io.DiskLruCache, 1, 2, ]`, `allocator 3.x is not supported` 경고와 함께 앱이 멈추는 문제를 이미지 캐시 journal 충돌 가능성으로 분리했다. `ImageLoaderProvider`에서 기존 `cacheDir/image_cache`를 앱 시작 시 삭제하고, 신규 OkHttp image cache directory를 `cacheDir/coil_image_cache`로 분리했다. 구현 전 `ImageLoaderProviderTest`는 cache directory 상수 미정의로 RED 컴파일 실패했고, 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.ImageLoaderProviderTest"`가 BUILD SUCCESSFUL임을 확인했다. - 2026-06-05: Phase 9.4 회귀 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐고 lint 실패는 없었다. - 2026-06-05: Phase 9.4 리뷰에서 `ImageLoaderProviderTest`가 상수명 차이만 검증해 `init()`의 legacy cache 삭제와 신규 cache 생성 계약을 충분히 고정하지 못한다는 차단 이슈를 확인했다. 테스트를 보강해 Robolectric 환경에서 `cacheDir/image_cache/journal`을 만든 뒤 `ImageLoaderProvider.init(context)` 호출 후 legacy directory 삭제와 `coil_image_cache` 생성까지 검증하도록 수정했다. 보강 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.common.ImageLoaderProviderTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 재실행했고 모두 BUILD SUCCESSFUL을 확인했다. - 2026-06-05: Phase 9.5로 `BannerView.setItems()` 직후 앱이 멈추는 문제를 배너 모델 payload 누락과 `BannerAdapter`의 huge range notify 가능성으로 분리했다. `BannerItem`을 삭제하지 않고 `HomeRecommendationBannerUiModel`과 동일한 payload(`imageUrl`, `eventItem`, `creatorId`, `seriesId`, `link`)를 담도록 확장해 공용 위젯 독립성을 유지했으며, `HomeBannerBinder`는 synthetic id 변환 없이 홈 배너 UI model과 `BannerItem`을 변환하도록 단순화했다. 또한 carousel 목록에서 `itemCount == Int.MAX_VALUE`여도 `notifyItemRangeInserted(0, Int.MAX_VALUE)`와 `notifyItemRangeChanged(0, Int.MAX_VALUE)`를 호출하지 않도록 했고, 단일 배너는 specific notify를 쓰되 `Int.MAX_VALUE` 가상 adapter 상태에서만 `NotifyDataSetChanged` lint를 좁은 helper에 제한했다. 구현 전 `BannerViewTest`는 기존 `BannerItem`이 변경된 홈 배너 payload를 담지 못해 클릭 데이터 보존을 검증할 수 없는 RED 상태였고, 구현 후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.banner.BannerViewTest"`가 BUILD SUCCESSFUL임을 확인했다. - 2026-06-05: Phase 9.5 회귀 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`를 추가 실행했고 모두 BUILD SUCCESSFUL을 확인했다. `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐고 lint 실패는 없었다. ### Phase 12: `처음부터 함께 성장!` 섹션 제거 #### Phase 12B 식별자 별칭 기존 완료 이력의 제목과 체크박스는 보존한다. 이후 리뷰·회귀 수정·검증 기록에서 이 두 번째 Phase 12를 참조할 때는 아래 고유 별칭을 사용한다. | 역사적 식별자 | 고유 별칭 | 이후 참조 규칙 | |---|---|---| | 두 번째 `Phase 12: 처음부터 함께 성장! 섹션 제거` | `P12B` | first audio 제거 Phase는 `P12B`로 표기한다. | | 두 번째 Phase 12의 `Task 10.1` | `P12B-T1` | 홈 추천 Response/UI first audio 제거 항목은 `P12B-T1`로 표기한다. | | 두 번째 Phase 12의 `Task 10.2` | `P12B-T2` | first audio 전체보기와 리소스 제거 항목은 `P12B-T2`로 표기한다. | - [x] **Task 10.1: 홈 추천 Response와 UI에서 firstAudio 제거** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiState.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` - 삭제: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFirstAudioAdapter.kt` - 삭제: `app/src/main/res/layout/item_home_first_audio_content.xml` - 검증 기록: 2026-06-29 기존 코드에서 `rvHomeFirstAudioContents`, `llHomeFirstAudioSection`, `viewHomeFirstAudioTitle` binding 미해결로 RED 컴파일 실패를 확인했다. 제거 후 targeted unit test와 compile이 포함된 Gradle 실행이 BUILD SUCCESSFUL임을 확인했다. - [x] **Task 10.2: 홈 first audio 전체보기와 리소스 제거** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/data/ContentOverviewModels.kt` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/content/overview/model/ContentOverviewUiModels.kt` - 수정: `app/src/main/res/values/strings.xml`, `values-en/strings.xml`, `values-ja/strings.xml` - 검증 기록: 2026-06-29 `ContentOverviewType.FIRST_AUDIO_CONTENT`와 `home_recommendation_section_first_audio_contents` production 참조를 제거하고, `NEW_AND_HOT_AUDIO` 경로는 유지했다. #### Task R12B.1: 중복 Phase·Task 식별자 추적성 복구 **Goal 실행 `P12B-R1`:** 두 번째 `Phase 12`와 그 안의 `Task 10.1`·`Task 10.2`를 기존 완료 이력을 보존한 채 고유 별칭으로 식별할 수 있게 해 리뷰·회귀 Task 연결의 모호성을 제거한다. - **시작 조건:** `REV-P12B-001` 확정 및 `reviews/phase12-first-audio-removal-review.md` 판정 완료. - **완료 증거:** 역사적 식별자와 신규 고유 별칭 대응표 추가, 이후 참조 규칙 명시, 중복 식별자 검색 결과와 `git diff --check` 성공, 리뷰 보고서 수정 후 검증 기록 누적. - **범위 밖:** 기존 완료 Phase·Task 체크박스 재개방, 기존 식별자 삭제, first audio production 코드 또는 리소스 변경. **Files:** - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-first-audio-removal-review.md` - [x] **문서 검증 예외:** production 동작 변경이 없는 추적성 정정이므로 실패 재현 테스트 대신 `Phase 12` 및 `Task 10.*` 중복 검색 결과를 재현 근거로 사용한다. - [x] 기존 제목과 완료 기록은 유지하고, 두 번째 Phase 12 및 하위 Task에 `P12B`, `P12B-T1`, `P12B-T2` 고유 별칭 대응표와 이후 참조 규칙을 추가한다. - [x] Phase별 리뷰·회귀 문서가 고유 별칭으로 연결되는지 `rg`로 확인하고 `git diff --check`를 실행한다. - [x] `REV-P12B-001` 보고서의 `수정 후 검증 기록`에 변경 파일, 검증 명령, 결과와 남은 항목을 누적한다. - 검증 기록: 2026-07-31 문서 검증 예외로 `rg -n "^(### Phase 12:|## Phase 12:|#### Task 10\.|- \[x\] \*\*Task 10\.)" "docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md"`를 실행해 첫 번째 `Phase 12`, 두 번째 `Phase 12`, Phase 10 `Task 10.1`, 두 번째 Phase 12의 `Task 10.1`·`Task 10.2` 충돌을 재현했다. 기존 완료 제목과 체크박스는 유지하고 `P12B`, `P12B-T1`, `P12B-T2` 별칭 표와 이후 참조 규칙만 추가했다. - 검증 기록: 2026-07-31 `rg -n "P12B|P12B-T1|P12B-T2|P12B-R1|REV-P12B-001" "docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md" "docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-first-audio-removal-review.md"`로 별칭과 리뷰 연결을 확인했고, `git diff --check`는 출력 없이 성공했다. `REV-P12B-001` 보고서의 `수정 후 검증 기록`에 변경 파일, 검증 명령, 결과, 남은 항목 없음 상태를 누적했다. #### Task R12B.2: PRD 활성 영역의 first audio 제거 계약 동기화 **Goal 실행 `P12B-R2`:** `REV-P12B-002`에서 확인한 PRD 활성 섹션 도식·Metrics·Open Questions의 first audio 잔존 문구를 Phase 12B 제거 계약과 일치시킨다. - **시작 조건:** `REV-P12B-002` 확정, `P12B-T1`·`P12B-T2` production 제거 완료 유지. - **완료 증거:** PRD의 현재 Problem·섹션 도식·Metrics·Open Questions에서 제거된 `firstAudioContents`·`HomeFirstAudioContentItem`·`releaseDate` 활성 계약 제거, 2026-06-01 구현 이력과 2026-06-29 제거 기록 보존, production 금지 참조 검색과 `git diff --check` 성공. - **범위 밖:** 과거 검증 이력 삭제, `NEW_AND_HOT_AUDIO` 기능, production·resource·test 변경. - **TDD 예외 사유:** production 제거는 이미 완료되었고 현재 PRD 정합성만 정정하므로 `rg` 금지 참조 검색과 문서 재독을 대체 검증으로 사용한다. **Files:** - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-first-audio-removal-review.md` - [x] PRD 활성 영역에 남은 first audio 참조와 production 미존재를 `rg`로 재현한다. - [x] Problem·섹션 도식·Metrics·Open Questions를 제거 계약에 맞게 정정하고 과거 이력은 그대로 보존한다. - [x] `rg -n "firstAudioContents|HomeFirstAudioContentItem|releaseDate"` 결과를 과거 기록과 활성 계약으로 분류하고 `git diff --check` 결과를 누적한다. - 검증 기록: 2026-07-31 `rg -n "firstAudioContents|HomeFirstAudio|FIRST_AUDIO_CONTENT|AudioContentCardView|releaseDate|첫 오디오|처음부터 함께 성장" "docs/20260601_메인_홈_추천_UI와_API_연동/prd.md" app/src/main`로 PRD 활성 영역의 first audio 잔존 참조와 production 미존재를 재현했다. 활성 Problem, 섹션 도식, Metrics, Open Questions에서는 first audio 완료 조건을 제거했고, 2026-06-01 과거 기록 및 2026-06-29 제거 기록은 보존했다. - 검증 기록: 2026-07-31 `rg -n "firstAudioContents|HomeFirstAudioContentItem|releaseDate|첫 오디오|처음부터 함께 성장" "docs/20260601_메인_홈_추천_UI와_API_연동/prd.md" app/src/main/java/kr/co/vividnext/sodalive/v2/main/home app/src/main/res/values app/src/main/res/layout` 결과는 PRD 과거 기록 및 2026-06-29 제거 기록만 반환하고, v2 home production/resource 제거 대상 참조는 반환하지 않았다. `git diff --check`는 출력 없이 성공했다. ### Phase 13: 커뮤니티 상세 라우팅 변경 - [x] **Task 13.1: 방금 활동한 크리에이터 COMMUNITY 라우팅 변경** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `CreatorActivityType.Community` route의 `targetId`를 커뮤니티 게시글 `postId`로 해석한다. - `CreatorActivityType.Community` route intent는 `CreatorChannelCommunityDetailActivity.newIntent(context, postId)`로 생성한다. - `targetId`가 없거나 0 이하이면 기존처럼 이동하지 않는다. - 검증: - `HomeMainFragmentLayoutTest`에서 COMMUNITY route intent component와 `extra_post_id` 전달을 확인한다. - 검증 기록: - 2026-07-10: `HomeMainFragmentLayoutTest`의 최근 활동 COMMUNITY intent 기대값을 `CreatorChannelCommunityDetailActivity`와 `extra_post_id`로 먼저 변경해 기존 `CreatorCommunityAllActivity` 구현에서 RED 실패를 확인했다. 이후 `HomeRecommendationRecentlyActiveCreatorRoute.Community`의 `targetId`를 `postId`로 해석하고 `CreatorChannelCommunityDetailActivity.newIntent(context, postId)`를 생성하도록 수정했다. - [x] **Task 13.2: 인기 커뮤니티 item 라우팅 변경** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt` - 구현 내용: - 인기 커뮤니티 item 클릭 시 `FeedItem.Community.postId`를 사용해 `CreatorChannelCommunityDetailActivity`로 이동한다. - 기존 `CreatorCommunityAllActivity`와 `EXTRA_COMMUNITY_CREATOR_ID`, `EXTRA_COMMUNITY_POST_ID`, `EXTRA_COMMUNITY_EXIST_ORDERED` 전달을 제거한다. - `postId`가 없거나 숫자로 변환되지 않으면 기존처럼 이동하지 않는다. - 검증: - `HomeMainFragmentLoginGuardSourceTest`에서 invalid `postId` return이 로그인 가드보다 먼저 유지되는지 확인한다. - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recent activity route creates expected intents" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest"`를 실행한다. - 검증 기록: - 2026-07-10: `HomeMainFragmentLoginGuardSourceTest`에 인기 커뮤니티가 `CreatorChannelCommunityDetailActivity.newIntent(requireContext(), postId)`를 사용하고 기존 `CreatorCommunityAllActivity`/커뮤니티 목록 extra를 쓰지 않는 source 검증을 추가했다. 기존 구현에서 RED 실패를 확인한 뒤 `HomeMainFragment.openPopularCommunityPost()`가 `postId`만 검증하고 상세 Activity를 열도록 수정했다. - 2026-07-10: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home recent activity route creates expected intents" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest"` 실행 결과 BUILD SUCCESSFUL을 확인했다. ### Phase 14: `genreCreators` UI 미표시 - [x] **Task 14.1: 추천 탭 장르별 크리에이터 섹션 숨김** - 수정: `app/src/main/res/layout/fragment_v2_main_home.xml` - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - 구현 내용: - `ll_home_genre_creator_section`의 기본 visibility를 `gone`으로 둔다. - `bindGenreCreatorSection()`은 API 응답 데이터와 무관하게 섹션을 `GONE`으로 유지하고 adapter에는 빈 그룹을 제출한다. - `genreCreators` DTO/API/UI model/mapper 계약은 유지하고, `cheerCreators` 섹션은 변경하지 않는다. - 검증 기록: - 2026-07-12: `HomeMainFragmentLayoutTest`에 `home recommendation does not display genre creator section`을 먼저 추가해 기존 구현에서 `HomeMainFragmentLayoutTest.kt:152` assertion 실패로 RED를 확인했다. 이후 XML 기본 숨김과 Fragment 바인딩의 `View.GONE`/`emptyList()` 제출을 적용했고 동일 테스트가 BUILD SUCCESSFUL로 GREEN 전환됨을 확인했다. ### Phase 15: 추천 섹션 타이틀 chevron 숨김 - [x] **Task 15.1: 최근 데뷔와 AI 캐릭터 섹션 chevron 숨김** - 수정: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` - 수정: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentSourceTest.kt` - 구현 내용: - `최근 데뷔한 크리에이터` 섹션 타이틀은 `view_section_title` chevron을 표시하지 않는다. - `크리에이터와 이야기를 나눠요!` 섹션 타이틀은 `view_section_title` chevron을 표시하지 않는다. - 섹션 목록 표시와 item 터치 동작, 다른 섹션의 chevron 정책은 변경하지 않는다. - 검증: - `HomeMainFragmentSourceTest`에서 두 섹션 title 설정이 `showMore = true`를 사용하지 않는지 확인한다. - `HomeMainFragmentLayoutTest` 전체로 홈 추천 인접 layout 계약을 확인한다. - 검증 기록: - 2026-07-12: `HomeMainFragmentSourceTest`에 `홈 추천 최근 데뷔와 AI 캐릭터 섹션 타이틀은 chevron을 숨긴다`를 먼저 추가해 기존 구현에서 `HomeMainFragmentSourceTest.kt:40` assertion 실패로 RED를 확인했다. 이후 `HomeMainFragment.setUpSectionTitles()`에서 `viewHomeRecentDebutTitle`, `viewHomeAiCharacterTitle`의 `showMore = true`를 제거해 기본 `false` 정책을 적용했다. ### Phase 16: 방금 활동한 크리에이터 LIVE 라우팅 **Phase 결과:** `방금 활동한 크리에이터`의 `LIVE` item을 터치하면 `targetId` 유무에 따라 기존 공통 라이브 룸 진입 또는 크리에이터 채널 이동이 실행된다. **선행 조건:** `prd.md`의 `2026-07-30 변경: 최근 활동 LIVE 라우팅` 요구사항 승인과 서버의 필수 양수 `HomeActiveCreatorItem.creatorId: Long` 계약 확정. **Phase 완료 조건:** `P16-T1`과 `P16-GATE` 완료, Task와 `Verification Log`에 실제 검증 결과 누적. #### Task 16.1: LIVE route와 공통 액션 연결 **Goal 실행 `P16-T1`:** 최근 활동 `LIVE` item이 유효한 `targetId > 0L`가 있으면 라이브 룸으로 진입하고, `targetId == null && creatorId > 0L`이면 크리에이터 채널로 이동하며, 잘못된 `targetId` 또는 `creatorId`에서는 이동하지 않는다. - **시작 조건:** Phase 16 선행 조건 충족. - **완료 증거:** RED/GREEN 체크박스 완료, focused test와 직접 영향 회귀 성공, 문서 검증 기록 누적. - **범위 밖:** `LiveActionCoordinator`, `CreatorAction`, `V2AccessGuard`의 공통 정책 변경과 다른 activity type 라우팅 변경. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt` - Modify: `docs/agent-guides/build-test-style.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` **Interfaces:** - Consumes: `HomeActiveCreatorItem(activityType, targetId, creatorId)`, `LiveActionCoordinator.enterLiveRoom(roomId: Long)`, `CreatorActionCommand.Profile(creatorId: Long)`. - Produces: `HomeRecommendationRecentlyActiveCreatorUiModel.creatorId: Long`, `HomeRecommendationRecentlyActiveCreatorRoute.LiveRoom(roomId: Long)`, `HomeRecommendationRecentlyActiveCreatorRoute.CreatorChannel(creatorId: Long)`. - [x] **RED:** `HomeMainFragmentLayoutTest`와 `HomeMainFragmentLoginGuardSourceTest`에 다음 실패 계약을 추가한다. - `HomeActiveCreatorItem.creatorId`가 `HomeRecommendationRecentlyActiveCreatorUiModel.creatorId`로 보존된다. - `LIVE + targetId > 0L`은 `HomeRecommendationRecentlyActiveCreatorRoute.LiveRoom(targetId)`를 반환한다. - `LIVE + targetId == null + creatorId > 0L`은 `HomeRecommendationRecentlyActiveCreatorRoute.CreatorChannel(creatorId)`을 반환한다. - `LIVE + targetId == null + creatorId <= 0L`은 `null`을 반환한다. - `HomeMainFragment.onRecentActivityClick()`은 `LiveRoom`을 `liveActionCoordinator.enterLiveRoom(route.roomId)`으로, `CreatorChannel`을 `handleCreatorAction(CreatorActionCommand.Profile(route.creatorId))`로 전달한다. - 기존 `LIVE_REPLAY`, `AUDIO`, `COMMUNITY`, unknown, invalid `targetId` 기대값은 유지한다. - [x] **RED 확인:** 아래 focused test를 실행해 `creatorId`, `LiveRoom`, `CreatorChannel` 미정의 또는 기존 `LIVE -> null` 기대값 불일치로 실패하는지 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" ``` - [x] **GREEN:** RED를 통과시키는 최소 구현을 작성한다. - DTO와 최근 활동 UI model에 필수 `creatorId: Long`을 추가하고 mapper에서 그대로 전달한다. - 최근 활동 route에 `LiveRoom(roomId)`과 `CreatorChannel(creatorId)`을 추가한다. - `CreatorActivityType.Live`는 `targetId > 0L`이면 `LiveRoom(targetId)`, `targetId == null`이면 `creatorId.takeIf { it > 0L }`에 대해서만 `CreatorChannel(creatorId)`을 반환하고, `targetId <= 0L`이면 fallback 없이 `null`을 반환한다. - Fragment는 `LiveRoom`에 기존 `LiveActionCoordinator.enterLiveRoom()`을 사용하고 `CreatorChannel`에 기존 `CreatorActionCommand.Profile`을 사용한다. - 홈 전용 로그인·성인 콘텐츠 분기를 새로 만들지 않는다. - `build-test-style.md`에 위 두 home test class를 함께 실행하는 Phase 16 focused test 예시를 추가한다. - [x] **GREEN 확인:** RED 확인과 같은 focused test를 다시 실행해 route, mapper, Fragment 공통 액션 연결이 모두 성공하는지 확인한다. - [x] **REFACTOR:** 이번 Task가 만든 중복만 정리하고 아래 직접 영향 회귀와 품질 검증 결과를 Task 검증 기록 및 `Verification Log`에 누적한다. 전체 unit test는 공통 access/action 구현을 변경하지 않고 home route 소비만 추가하므로 생략하며, 대신 home focused test와 기존 live/creator action 회귀를 실행한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" \ --tests "kr.co.vividnext.sodalive.v2.live.action.*" \ --tests "kr.co.vividnext.sodalive.v2.creator.action.*" ./gradlew :app:compileDebugKotlin ./gradlew :app:ktlintCheck git diff --check ``` ### 완료 조건 - [x] `HomeActiveCreatorItem.creatorId`가 DTO에서 UI model까지 필수 `Long`으로 보존된다. - [x] `LIVE`의 두 route와 invalid `creatorId` no-op이 자동 테스트로 검증된다. - [x] 라이브 룸 route가 `LiveActionCoordinator`를 사용해 기존 로그인·본인인증·콘텐츠 보기 설정 정책을 우회하지 않는다. - [x] 다른 activity type의 route 회귀 테스트가 성공한다. - [x] 사용자 직접 지시가 없으므로 커밋하지 않는다. ### 검증 방법 #### Phase 16 Gate **Goal 실행 `P16-GATE`:** Phase 16 route 분기, 공통 접근 흐름 재사용, 직접 영향 회귀를 최종 판정한다. - **시작 조건:** `P16-T1` 체크박스와 검증 기록 완료. - **완료 증거:** Task 16.1 REFACTOR 명령이 모두 성공하고 아래 수동 대조를 완료한다. - **범위 밖:** Gate 통과를 위한 test 삭제·완화, 공통 access/action 로직 수정, 관련 없는 홈 코드 정리. 수동 대조: - [x] `targetId > 0L`인 `LIVE` route가 `LiveActionCoordinator.enterLiveRoom(targetId)`에 연결된다. - [x] `targetId == null && creatorId > 0L`인 `LIVE` route가 `CreatorActionCommand.Profile(creatorId)`에 연결된다. - [x] `targetId == null && creatorId <= 0L`에서 액션이 실행되지 않는다. - [x] Git diff가 Phase 16 Files와 승인된 PRD·test guide 범위 안에 있고 커밋이 생성되지 않았다. 검증 기록: - 2026-07-30: 사용자의 `진행`은 production 구현이 아니라 `plan-task.md` 반영을 의미한다는 설명에 따라 Phase 16 구현을 중단했다. 설명을 받기 전에 `HomeMainFragmentLayoutTest`에 `creatorId` DTO/UI model 보존, `LIVE + targetId`의 `LiveRoom`, `LIVE + targetId == null`의 `CreatorChannel`, `creatorId <= 0L` no-op RED 계약을 추가했고, `HomeMainFragmentLoginGuardSourceTest`에 최근 활동 route가 기존 Live/Creator Action을 사용하는 계약을 추가했다. 사용자가 이미 작성된 코드는 삭제하지 않도록 요청해 두 테스트 변경은 유지한다. focused test 명령은 실행 중 사용자 중단으로 종료되어 성공·실패 출력이 없으므로 `RED 확인`은 미완료 상태로 남긴다. `git diff --name-only` 기준 변경은 PRD, 계획 문서, 위 두 테스트 파일뿐이며 `app/src/main` production 코드는 변경되지 않았다. 커밋은 생성하지 않았다. - 2026-07-30: Phase 16 구현을 재개해 기존 RED 계약을 실행했다. focused RED는 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest"`에서 `HomeActiveCreatorItem.creatorId`, `HomeRecommendationRecentlyActiveCreatorUiModel.creatorId`, `LiveRoom`, `CreatorChannel` 미정의 컴파일 실패로 확인했다. GREEN 구현으로 `HomeActiveCreatorItem.creatorId`를 DTO에 추가하고 최근 활동 UI model까지 보존했으며, `CreatorActivityType.Live` route를 `targetId > 0L`이면 `LiveRoom(roomId)`, `targetId == null && creatorId > 0L`이면 `CreatorChannel(creatorId)`, 그 외에는 `null`로 분기했다. 리뷰에서 `targetId <= 0L`일 때 creator fallback이 실행될 수 있는 차단 이슈가 확인되어 `targetId = 0L/-1L + creatorId > 0L` RED 테스트를 추가했고, `CreatorActivityType.Live`는 `targetId == null`일 때만 creator fallback을 허용하도록 보완했다. `HomeMainFragment.onRecentActivityClick()`은 `LiveRoom`을 `liveActionCoordinator.enterLiveRoom(route.roomId)`로, `CreatorChannel`을 `handleCreatorAction(CreatorActionCommand.Profile(route.creatorId))`로 전달해 공통 접근 정책을 재사용한다. focused GREEN, 직접 영향 회귀(`HomeMainFragmentLayoutTest`, `HomeMainFragmentLoginGuardSourceTest`, `v2.live.action.*`, `v2.creator.action.*`), `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`가 모두 성공했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고다. `./gradlew :app:installDebug`는 SM-G960N 기기에 설치 성공했고, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`로 debug 앱 런처 실행을 확인했다. 실제 추천 LIVE item 터치는 서버 데이터/로그인 상태가 필요한 경로라 이번 검증에서는 자동 route test와 source 계약으로 대체했다. 커밋은 생성하지 않았다. ## Phase 17: 모두 팔로우 확인 절차 **Phase 결과:** 여러 크리에이터를 한 번에 팔로우하는 공통 동작은 API 호출 전에 기존 `V2ModalDialog` 확인창을 거치고, 현재 노출 중인 `최근 응원이 많은 크리에이터` 섹션에서는 사용자가 `팔로우 하기`를 선택한 경우에만 기존 모두 팔로우 API가 실행된다. **선행 조건:** `prd.md`의 `2026-07-31 변경: 모두 팔로우 확인 절차` 승인, 기존 `V2ModalDialog`의 취소·확인 2버튼 및 중복 클릭 방지 계약 유지. **Phase 완료 조건:** `P17-T1`과 `P17-GATE` 완료, Task 검증 기록과 `Verification Log`에 실제 결과 누적. ### 구현 항목 #### Task 17.1: 응원 크리에이터 모두 팔로우 확인창 연결 **Goal 실행 `P17-T1`:** `최근 응원이 많은 크리에이터`의 기본 상태 모두 팔로우 버튼은 확인창만 열고, 취소에서는 요청하지 않으며, 확인에서만 기존 `creatorIds`로 팔로우 API를 정확히 한 번 요청한다. - **시작 조건:** Phase 17 선행 조건 충족. - **완료 증거:** RED/GREEN 체크박스 완료, 한국어·영어·일본어 resource 및 확인/취소/API 호출 시점 자동 검증, focused 회귀와 수동 확인 결과 누적. - **범위 밖:** 모두 팔로우 API·request/response·ViewModel 로딩/성공/실패 처리 변경, `V2ModalDialog` 공통 구현·layout 변경, 숨겨진 `genreCreators` UI 복원, 신규 범용 wrapper 또는 dependency 추가, 로그인 정책 변경. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` - Modify: `app/src/main/res/values/strings.xml` - Modify: `app/src/main/res/values-en/strings.xml` - Modify: `app/src/main/res/values-ja/strings.xml` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - Verify without modification: `app/src/test/java/kr/co/vividnext/sodalive/v2/components/modal/V2ModalDialogTest.kt` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` **Interfaces:** - Consumes: `HomeCheerCreatorAdapter(onFollowAllClick: (List) -> Unit)`, `V2ModalDialog`, `HomeRecommendationViewModel.followCreators(sectionKey: String, creatorIds: List)`, `SECTION_KEY_CHEER_CREATORS`, `screenWidth`. - Produces: `HomeMainFragment.showCheerCreatorsFollowAllConfirmDialog(creatorIds: List)`과 한국어·영어·일본어 `home_recommendation_follow_all_confirm_title`, `home_recommendation_follow_all_confirm_description`, `home_recommendation_follow_all_confirm_button` string resource. - [x] **RED:** `HomeMainFragmentLayoutTest`에 다음 실패 계약을 추가한다. - 응원 크리에이터 adapter callback은 `followCreators()`를 직접 호출하지 않고 `showCheerCreatorsFollowAllConfirmDialog(creatorIds)`를 호출한다. - 확인창은 `V2ModalDialog`를 사용하고 title/desc/confirm에 신규 resource, cancel에 기존 `R.string.cancel`을 전달한다. - `confirmButtonClick`만 `homeRecommendationViewModel.followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds)`를 호출하며 cancel callback은 API를 호출하지 않는다. - 한국어·영어·일본어 `strings.xml`에 PRD의 resource key와 확정 번역이 모두 존재한다. - 기존 완료 상태와 빈 `creatorIds` no-op은 `HomeFollowAllButtonBinder` 및 adapter 회귀 계약으로 유지된다. - [x] **RED 확인:** 아래 focused test를 실행해 신규 resource와 `showCheerCreatorsFollowAllConfirmDialog()`가 없고 adapter callback이 `followCreators()`를 직접 호출하므로 의도한 실패를 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" ``` - [x] **GREEN:** RED를 통과시키는 최소 구현을 작성한다. - 세 locale에 PRD에서 확정한 title/description/confirm string resource만 추가하고 취소는 기존 `cancel`을 재사용한다. - `HomeMainFragment`의 응원 크리에이터 adapter callback을 `showCheerCreatorsFollowAllConfirmDialog(creatorIds)` 호출로 변경한다. - `showCheerCreatorsFollowAllConfirmDialog()`는 기존 `V2ModalDialog`의 2버튼 구성을 표시하며 `confirmButtonClick`에서만 `followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds)`를 호출한다. - Dialog는 `show(screenWidth)`로 표시하고, 취소·바깥 영역·시스템 뒤로가기 및 빠른 중복 확인 동작은 `V2ModalDialog`의 기존 계약을 그대로 사용한다. - 기존 `HomeFollowAllButtonBinder`의 완료 상태·빈 목록 no-op과 `HomeRecommendationViewModel`의 로딩·성공·실패 처리는 변경하지 않는다. - [x] **GREEN 확인:** RED 확인과 같은 focused test를 다시 실행해 직접 API 호출 제거, Dialog 연결과 세 locale resource가 모두 성공하는지 확인한다. - [x] **REFACTOR:** 이번 Task가 만든 중복만 정리하고 아래 직접 영향 회귀와 품질 검증 결과를 Task 검증 기록 및 `Verification Log`에 누적한다. 공통 모달 구현과 API/ViewModel 계약을 변경하지 않으므로 전체 unit test는 생략하고 home focused test, 기존 modal test, resource merge와 compile로 영향 범위를 검증한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" \ --tests "kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialogTest" ./gradlew :app:mergeDebugResources ./gradlew :app:compileDebugKotlin ./gradlew :app:ktlintCheck git diff --check ``` #### Task R17.1: 모두 팔로우 회귀 테스트 이름 규칙 정정 **Goal 실행 `P17-R1`:** `REV-P17-001`의 신규 영문 테스트명 2건을 한글 문장으로 정정하고 Dialog 선행·다국어 assertion을 그대로 유지한다. - **시작 조건:** `REV-P17-001` 확정 및 `P17-T1` 자동 검증 완료. - **완료 증거:** 대상 테스트명을 한글 문장으로 정정, `HomeMainFragmentLayoutTest`·`V2ModalDialogTest`·`ktlintCheck`·`git diff --check` 성공, 리뷰 보고서 검증 기록 누적. - **범위 밖:** production Dialog·ViewModel·API 동작, assertion 완화, 다른 기존 영문 테스트명 일괄 정리. - **TDD 예외 사유:** 신규 회귀 테스트의 표현 규칙만 정정하므로 RED/GREEN 대신 정정 전·후 동일 assertion 성공을 검증한다. **Files:** - Modify: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase17-follow-all-confirmation-review.md` - [x] `cheer creator follow all opens confirm dialog before api request`, `follow all confirm strings are localized` 테스트명을 규칙 위반 근거로 기록한다. - [x] 각 테스트명을 `응원 크리에이터 모두 팔로우는 API 요청 전에 확인창을 연다`, `모두 팔로우 확인창 문자열은 다국어로 제공한다`로 정정한다. - [x] `HomeMainFragmentLayoutTest`·`V2ModalDialogTest`, `./gradlew :app:ktlintCheck`, `git diff --check`를 실행하고 결과를 누적한다. - 검증 기록: 2026-07-31 `rg -n "home popular community adapter does not load original image for locked paid post|cheer creator follow all opens confirm dialog before api request|follow all confirm strings are localized|유료 미구매 인기 커뮤니티는 원본 이미지를 로드하지 않는다|응원 크리에이터 모두 팔로우는 API 요청 전에 확인창을 연다|모두 팔로우 확인창 문자열은 다국어로 제공한다" app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt`로 영문 테스트명 제거와 한글 테스트명 반영을 확인했다. Production Dialog·ViewModel·API 동작과 assertion은 변경하지 않았다. - 검증 기록: 2026-07-31 직접 영향 회귀 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialogTest"`, `./gradlew :app:ktlintCheck`, `git diff --check`를 실행했고 모두 성공했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고다. #### Task R17.2: 모두 팔로우 확인창 로그인 가드 순서 수정 **Goal 실행 `P17-R2`:** `최근 응원이 많은 크리에이터` 모두 팔로우 터치 시 미로그인 상태에서는 확인 Dialog보다 기존 로그인 가드가 먼저 동작하도록 순서를 복구한다. - **시작 조건:** 수동 확인에서 `모두 팔로우` 터치 시 로그인 가드보다 확인 Dialog가 먼저 표시되는 버그 확인. - **완료 증거:** 로그인 가드가 Dialog 표시보다 먼저 호출되는 RED/GREEN source regression, 기존 Dialog 선행/API confirm-only 계약 회귀, compile·ktlint·`git diff --check` 성공. - **범위 밖:** `V2ModalDialog` 공통 구현 변경, 모두 팔로우 API/ViewModel 변경, 신규 로그인 정책·공통 abstraction 추가. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase17-follow-all-confirmation-review.md` - [x] **RED:** `onCheerFollowAllClick` source에서 `ensureV2Access(AccessRequirement.Login)`이 `showCheerCreatorsFollowAllConfirmDialog(creatorIds)`보다 먼저 호출되어야 함을 테스트로 추가하고 현재 구현 실패를 확인한다. - [x] **GREEN:** `onCheerFollowAllClick`만 최소 수정해 로그인 가드 통과 후 확인 Dialog를 표시한다. - [x] 기존 확인 Dialog resource, 취소 no-op, confirm-only API 호출 계약 테스트와 `compileDebugKotlin`, `ktlintCheck`, `git diff --check`를 실행하고 결과를 누적한다. - 검증 기록: 2026-07-31 RED는 `HomeMainFragmentLoginGuardSourceTest.HomeMainFragment 응원 크리에이터 모두 팔로우는 확인창보다 로그인 가드를 먼저 실행한다`와 `HomeMainFragmentLayoutTest.응원 크리에이터 모두 팔로우는 API 요청 전에 확인창을 연다`를 추가·수정한 뒤 focused test에서 각각 `HomeMainFragmentLoginGuardSourceTest.kt:283`, `HomeMainFragmentLayoutTest.kt:1309` assertion 실패로 확인했다. GREEN은 `onCheerFollowAllClick`에서 `ensureV2Access(AccessRequirement.Login) { showCheerCreatorsFollowAllConfirmDialog(creatorIds) }`로 감싸는 최소 변경 후 동일 focused test BUILD SUCCESSFUL로 확인했다. - 검증 기록: 2026-07-31 직접 영향 회귀 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialogTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`를 실행했고 모두 성공했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고다. ### 완료 조건 - [x] 기본 상태 모두 팔로우 버튼 터치 직후 API 호출 없이 `V2ModalDialog`가 표시된다. - [x] `취소`는 Dialog만 닫고 API 호출과 버튼 상태 변경을 발생시키지 않는다. - [x] `팔로우 하기`는 Dialog를 닫은 뒤 기존 `cheerCreators.creatorId` 목록으로 API를 정확히 한 번 호출한다. - [x] API 성공·실패·로딩과 완료 버튼 상태는 기존 동작을 유지한다. - [x] 확인창 문구가 한국어·영어·일본어 resource로 제공되고 UI 문자열이 Kotlin에 하드코딩되지 않는다. - [x] 완료 상태·빈 목록에서는 Dialog와 API가 실행되지 않는다. - [x] `genreCreators` 섹션은 계속 숨김 상태이고 신규 공통 abstraction을 추가하지 않는다. ### 검증 방법 #### Phase 17 Gate **Goal 실행 `P17-GATE`:** Phase 17의 확인 전 요청 금지, 취소 no-op, 확인 후 단일 요청, 다국어와 기존 회귀를 최종 판정한다. - **시작 조건:** `P17-T1` 체크박스와 검증 기록 완료. - **완료 증거:** Task 17.1 REFACTOR 명령이 모두 성공하고 아래 수동 확인을 완료한다. - **범위 밖:** Gate 통과를 위한 test 삭제·완화, API/ViewModel/공통 모달 계약 변경, 관련 없는 홈 코드 정리. 수동 확인: - [x] 한국어·영어·일본어 각각에서 확인창 title/description/confirm/cancel 문구가 잘림이나 하드코딩 없이 표시된다. - [x] 기본 상태 버튼 터치 시 확인창만 표시되고 loading 및 API 요청이 시작되지 않는다. - [x] `취소` 선택 시 Dialog가 닫히고 버튼이 기본 상태로 유지된다. - [x] `팔로우 하기` 선택 시 Dialog가 닫히고 loading 후 기존 성공 또는 실패 feedback이 표시된다. - [x] 완료 상태 버튼과 빈 creator 목록에서는 확인창이 표시되지 않는다. - [x] Git diff가 Phase 17 Files와 승인된 PRD 범위 안에 있고 커밋이 생성되지 않았다. 계획 기록: - 2026-07-31: 사용자 인터뷰에서 모든 `모두 팔로우` 동작에 확인 절차를 적용하되 현재 구현은 `최근 응원이 많은 크리에이터` 섹션만 대상으로 하는 B안을 확정했다. 기존 `HomeMainFragment`의 직접 API 호출 경로와 `V2ModalDialog`의 취소·확인·중복 클릭 방지 계약을 확인해 Phase 17을 작성했다. 이번 요청 범위에 따라 production·test·resource 구현은 시작하지 않았으며 모든 구현 및 Gate 체크박스는 미완료로 유지한다. - 2026-07-31: Phase 17 구현으로 `HomeMainFragment`의 응원 크리에이터 모두 팔로우 callback을 기존 즉시 `followCreators()` 호출에서 `V2ModalDialog` 확인창 표시로 변경했다. 확인 버튼에서만 기존 `homeRecommendationViewModel.followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds)`를 호출하고, 취소 버튼은 기존 `R.string.cancel`과 `V2ModalDialog` 기본 닫기 동작을 재사용한다. 한국어·영어·일본어 `home_recommendation_follow_all_confirm_title`, `home_recommendation_follow_all_confirm_description`, `home_recommendation_follow_all_confirm_button` resource를 추가했다. ## Phase 18: 현재 진행 중인 라이브 `전체` 항목 상시 표시 **Phase 결과:** 메인 홈 추천 탭의 현재 진행 중인 라이브가 1개 이상이면 최대 20개의 라이브 뒤에 `전체` 항목이 항상 표시되고, 0개이면 기존처럼 라이브 섹션이 숨겨진다. **선행 조건:** `prd.md`의 `2026-07-31 변경: 현재 진행 중인 라이브 전체 항목 상시 표시` 승인, 기존 `HomeLiveAdapter`의 20개 상한·`MoreViewHolder` 디자인·`HomeOnAirLiveActivity` 이동 계약 유지. **Phase 완료 조건:** `P18-T1`과 `P18-GATE` 완료, Task 검증 기록과 `Verification Log`에 실제 결과 누적. ### 구현 항목 #### Task 18.1: 라이브가 1개 이상이면 `전체` 항목 추가 **Goal 실행 `P18-T1`:** `HomeLiveAdapter`가 빈 목록에는 항목을 만들지 않고, 라이브가 1개 이상이면 라이브 개수와 관계없이 마지막에 기존 `전체` 항목을 추가한다. - **시작 조건:** Phase 18 선행 조건 충족. - **완료 증거:** RED/GREEN 체크박스 완료, 0개·1개·20개·21개 이상 경계 자동 검증, focused 회귀·compile·lint·diff 결과 누적. - **범위 밖:** 라이브 최대 표시 개수 변경, `전체` 항목 디자인·문구·클릭 목적지·로그인 가드 변경, 라이브 item 입장 흐름, 추천/전체 라이브 API 계약, 신규 abstraction·dependency 추가. **Files:** - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` **Interfaces:** - Consumes: `HomeLiveAdapter.submitItems(items: List)`, `MAX_VISIBLE_LIVE_COUNT = 20`, 기존 `MoreViewHolder`와 `setOnMoreClick(listener: (() -> Unit)?)`. - Produces: `showAllItem: Boolean` 내부 상태와 `0개 -> itemCount 0`, `1~20개 -> lives + 전체`, `21개 이상 -> live 20개 + 전체` adapter 계약. - [x] **RED:** `HomeMainFragmentLayoutTest`에 아래 경계 테스트를 추가한다. ```kotlin @Test fun `홈 라이브 어댑터는 라이브가 있으면 전체 항목을 추가한다`() { val context = ApplicationProvider.getApplicationContext() val parent = RecyclerView(context) parent.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) val adapter = HomeLiveAdapter() adapter.submitItems(emptyList()) assertEquals(0, adapter.itemCount) adapter.submitItems(listOf(liveItem(1L))) assertEquals(2, adapter.itemCount) val viewHolder = adapter.onCreateViewHolder(parent, adapter.getItemViewType(1)) assertEquals( context.getString(R.string.screen_home_theme_all), (viewHolder.itemView as TextView).text.toString() ) adapter.submitItems((1L..20L).map(::liveItem)) assertEquals(21, adapter.itemCount) } ``` - [x] **RED 확인:** 아래 focused test를 실행해 기존 `hasMore = items.size > MAX_VISIBLE_LIVE_COUNT` 조건에서는 라이브 1개의 `itemCount`가 `1`이라 `assertEquals(2, adapter.itemCount)`에서 의도대로 실패하는지 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" ``` - [x] **GREEN:** `HomeLiveAdapter`의 `hasMore` 이름과 조건만 아래처럼 최소 수정한다. ```kotlin private var showAllItem: Boolean = false fun submitItems(items: List) { this.items = items.take(MAX_VISIBLE_LIVE_COUNT) showAllItem = items.isNotEmpty() notifyDataSetChanged() } override fun getItemCount(): Int = items.size + if (showAllItem) 1 else 0 override fun getItemViewType(position: Int): Int { return if (showAllItem && position == itemCount - 1) VIEW_TYPE_MORE else VIEW_TYPE_LIVE } ``` - [x] **GREEN 확인:** RED 확인과 같은 focused test를 다시 실행해 신규 0개·1개 계약과 기존 21개 표시·22개 입력 cap 테스트가 모두 성공하는지 확인한다. - [x] **REFACTOR:** 새 abstraction 없이 변수명과 조건 변경만 유지하고 아래 직접 영향 회귀와 품질 검증 결과를 Task 검증 기록 및 `Verification Log`에 누적한다. 공용 계약·API·Fragment 흐름을 변경하지 않는 단일 adapter 분기는 `HomeMainFragmentLayoutTest`의 0개·1개·20개·21개·22개 경계로 직접 검증하고, 최종 branch 완료 검증에서 전체 unit test도 실행한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" ./gradlew :app:compileDebugKotlin ./gradlew :app:ktlintCheck git diff --check ``` 검증 기록: - 2026-07-31: 구현 전 baseline `HomeMainFragmentLayoutTest` 73개가 성공했다. 신규 `홈 라이브 어댑터는 라이브가 있으면 전체 항목을 추가한다` 테스트를 추가한 RED 실행은 총 74개 중 신규 1개만 `expected:<2> but was:<1>`로 실패해 기존 `items.size > MAX_VISIBLE_LIVE_COUNT` 조건을 재현했다. - 2026-07-31: `HomeLiveAdapter`의 `hasMore`를 `showAllItem`으로 변경하고 `showAllItem = items.isNotEmpty()`를 적용했다. 동일 focused test 74개, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`가 모두 `BUILD SUCCESSFUL`이었고 `git diff --check`는 출력 없이 성공했다. ktlint의 `.editorconfig disabled_rules` 및 Gradle deprecation warning은 기존 경고다. - 2026-07-31: REFACTOR에서는 신규 abstraction 없이 테스트명을 한글 의도에 맞게 정리하고 focused test와 ktlint를 재실행해 성공을 확인했다. 최종 완료 검증으로 전체 `:app:testDebugUnitTest --no-parallel --console=plain`도 실행해 162 suites, 1,199 tests, 실패 0·오류 0·skip 0을 확인했다. - 2026-07-31: Phase 18 코드 리뷰에서 PRD·Task와 `HomeLiveAdapter` 및 `HomeMainFragment` 호출 흐름을 대조해 확정 발견 사항이 없음을 확인했다. focused 74 tests, debug Kotlin compile, ktlint는 성공했고, 전체 unit test를 `--rerun-tasks`로 재실행해 162 suites·1,199 tests·실패 0·오류 0·skip 0을 확인했다. `lintDebug`는 변경되지 않은 `AndroidManifest.xml:316`의 `FacebookActivity` `MissingClass`를 포함한 기존 28 errors·767 warnings로 실패했으며, 이번 production 변경 파일에는 기존 `notifyDataSetChanged()` warning 외 신규 lint error가 없었다. 문서 기록 후 `./gradlew tasks --all --console=plain`도 성공했고 결과는 `reviews/phase18-live-all-item-review.md`에 기록했다. ### 완료 조건 - [x] 빈 `lives` 입력에서 adapter item이 0개이고 기존 Fragment의 라이브 섹션 숨김 정책이 유지된다. - [x] 라이브가 1개 이상 20개 이하이면 모든 라이브 뒤에 `전체` 항목이 표시된다. - [x] 라이브가 21개 이상이면 라이브 최대 20개와 `전체` 항목 1개만 표시된다. - [x] 기존 `전체` 디자인·문구·클릭 목적지와 라이브 item 입장 흐름이 변경되지 않는다. - [x] 변경 파일이 Phase 18 Files 범위 안이고 신규 파일·dependency·commit이 생성되지 않는다. ### 검증 방법 #### Phase 18 Gate **Goal 실행 `P18-GATE`:** Phase 18의 0개·1개·20개·21개 이상 경계와 기존 `전체` 항목 회귀를 최종 판정한다. - **시작 조건:** `P18-T1` 체크박스와 검증 기록 완료. - **완료 증거:** Task 18.1 REFACTOR 명령이 모두 성공하고 아래 대조를 완료한다. - **범위 밖:** Gate 통과를 위한 test 삭제·완화, `HomeMainFragment`·전체 라이브 화면·API 수정, 관련 없는 홈 코드 정리. 대조: - [x] `HomeLiveAdapter.submitItems()`가 표시 라이브를 `take(20)`으로 제한한다. - [x] `showAllItem`은 원본 `items.isNotEmpty()`로만 결정된다. - [x] 기존 `MoreViewHolder`, `createMoreView()`, `onMoreClick` 연결은 변경되지 않는다. - [x] Git diff가 Phase 18 Files와 승인된 PRD 범위 안에 있고 커밋이 생성되지 않았다. 계획 기록: - 2026-07-31: 사용자 인터뷰에서 라이브 0개는 기존처럼 섹션을 숨기고 1개 이상이면 최대 20개 뒤에 `전체` 항목을 항상 표시하는 정책을 확정했다. 기존 20개 상한·`전체` 디자인·클릭 목적지·로그인 가드·API 계약을 유지하고, `HomeLiveAdapter` 내부 상태명과 조건 및 `HomeMainFragmentLayoutTest` 경계 테스트만 변경하는 Phase 18을 작성했다. production·test 구현은 시작하지 않았다. - 2026-07-31: Phase 18을 TDD로 구현했다. RED는 라이브 1개에서 `itemCount`가 기대 2, 실제 1로 실패했고, GREEN은 `showAllItem = items.isNotEmpty()` 최소 변경 후 focused 74개 테스트 성공으로 확인했다. 전체 1,199 tests와 compile·ktlint·diff 검증도 성공했으며 기존 `MoreViewHolder`·클릭 연결·20개 cap은 변경하지 않았다. ## Phase 19: 현재 진행 중인 라이브 `전체` 항목 오른쪽 고정 **Phase 결과:** 메인 홈 추천 탭에서 라이브 목록만 가로 스크롤되고, `전체` 항목은 스크롤 위치와 관계없이 오른쪽 끝의 예약된 영역에 고정된다. **선행 조건:** Phase 18 완료, `prd.md`의 `2026-08-03 변경: 현재 진행 중인 라이브 전체 항목 오른쪽 고정` 승인. **Phase 완료 조건:** `P19-T1`, `P19-R1`, `P19-GATE` 완료, RED/GREEN 로직 회귀와 resource merge·compile·lint 결과를 Task 검증 기록과 `Verification Log`에 누적. ### 구현 항목 #### Task 19.1: 스크롤 목록과 고정 `전체` 항목 분리 **Goal 실행 `P19-T1`:** `HomeLiveAdapter`는 최대 20개의 라이브 item만 제공하고, `fragment_v2_main_home.xml`의 고정 `전체` 뷰가 기존 로그인 가드 경로를 호출한다. - **시작 조건:** Phase 19 선행 조건 충족. - **완료 증거:** RED/GREEN 체크박스 완료, 아래 S1~S3와 인접 라우팅 회귀 자동 검증, focused test·resource merge·compile·ktlint·diff 결과 누적. - **범위 밖:** `전체` 디자인·문구·클릭 목적지·로그인 가드 변경, 라이브 최대 20개 정책·item 순서·입장 흐름 변경, 추천/전체 라이브 API 변경, overlay·커스텀 LayoutManager·신규 abstraction·dependency 추가, 레거시 홈 수정. **Files:** - Modify: `app/src/main/res/layout/fragment_v2_main_home.xml` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeLiveAdapter.kt` - Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` - Test: `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt` - Modify: `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md` **Interfaces:** - Consumes: `HomeLiveAdapter.submitItems(items)`, `MAX_VISIBLE_LIVE_COUNT = 20`, `HomeMainFragment.openHomeOnAirLive()`의 기존 `ensureV2Access(AccessRequirement.Login)` 계약. - Produces: adapter의 `0개 -> 0`, `1~20개 -> 원본 개수`, `21개 이상 -> 20` item 계약과, `ll_home_live_section`의 스크롤 `rv_home_lives` + 고정 `tv_home_live_all` 형제 구조. **시나리오 계약:** - `S1` 빈 목록: adapter item 수가 0이고 `bindLiveSection()`의 기존 빈 목록 숨김 로직을 유지한다. 자동 증거는 `HomeMainFragmentLayoutTest`의 adapter 경계 로직 테스트다. - `S2` 라이브 1~20개: adapter item 수가 입력 개수와 같고 adapter가 별도 `전체` item을 만들지 않는다. 자동 증거는 `HomeMainFragmentLayoutTest`의 adapter 경계 로직 테스트다. - `S3` 라이브 21개 이상 및 인접 회귀: adapter item 수가 정확히 20이고, 고정 `전체` 터치는 기존 `openHomeOnAirLive()` 로그인 가드를 사용하며 라이브 item은 기존 `LiveActionCoordinator` 흐름을 유지한다. 자동 증거는 `HomeMainFragmentLayoutTest`와 `HomeMainFragmentLoginGuardSourceTest`다. - [x] **RED:** `HomeMainFragmentLayoutTest`의 adapter 계약을 `전체` item 포함 방식에서 0개·1개·20개·21개 이상 라이브 item 수만 검증하는 로직 테스트로 변경한다. View 크기·간격·부모 관계·visibility 같은 레이아웃 속성 테스트는 추가하지 않는다. - [x] **RED:** `HomeMainFragmentLoginGuardSourceTest`에 `setUpRecommendationAdapters()`가 `binding.tvHomeLiveAll.setOnClickListener { openHomeOnAirLive() }`를 연결하고 `liveAdapter.setOnMoreClick`을 사용하지 않는지 검증하는 한글 테스트를 추가한다. 기존 `openHomeOnAirLive()` 로그인 가드와 라이브 item 공통 Action 검증은 유지한다. - [x] **RED 확인:** 아래 focused test를 실행해 adapter의 추가 `전체` item과 기존 `setOnMoreClick` 연결 때문에 신규 로직 assertion이 의도대로 실패하는지 확인한다. ```bash ./gradlew :app:testDebugUnitTest \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" \ --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" \ --no-parallel --console=plain ``` - [x] **GREEN:** `fragment_v2_main_home.xml`에서 `ll_home_live_section`을 수평 컨테이너로 바꾸고, `rv_home_lives`는 `0dp + weight 1` 및 시작 padding을 사용하며, 기존 스타일과 끝 여백을 유지한 `tv_home_live_all`을 오른쪽 형제 뷰로 추가한다. - [x] **GREEN:** `HomeLiveAdapter`에서 `showAllItem`, `onMoreClick`, `MoreViewHolder`, `createMoreView()`, view type 분기를 제거하고 최대 20개의 라이브 item만 bind한다. `HomeMainFragment`는 고정 뷰 터치를 기존 `openHomeOnAirLive()`에 직접 연결한다. - [x] **GREEN 확인:** RED 확인과 같은 focused test를 다시 실행해 S1~S3와 기존 로그인 가드·라이브 item 라우팅 검증이 성공하는지 확인한다. - [x] **REFACTOR:** 이번 변경으로 사용되지 않는 import·adapter 코드만 제거하고 아래 직접 영향 회귀 및 품질 명령을 실행해 결과를 검증 기록에 누적한다. UI/계측 테스트, 기기·에뮬레이터 조작, 스크린샷·시각 QA는 실행하지 않는다. ```bash ./gradlew :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck \ --no-parallel --console=plain ./gradlew :app:testDebugUnitTest --no-parallel --console=plain git diff --check ``` 검증 기록: - 2026-08-03: 사용자 승인과 코드 조사 결과를 기준으로 Phase 19 계획을 작성했다. `전체`는 overlay가 아닌 예약된 형제 영역에 고정하고, Phase 18의 상시 표시·빈 목록 숨김·20개 cap·기존 디자인·라우팅 계약은 유지한다. production·test 구현은 시작하지 않았다. - 2026-08-03: `HomeMainFragmentLayoutTest`와 `HomeMainFragmentLoginGuardSourceTest`에 Phase 19 실패 테스트를 먼저 반영했다. focused 실행은 89 tests 중 신규 계약 4개만 실패했다. 실패는 수평 컨테이너 미적용(`HomeMainFragmentLayoutTest.kt:1043`), 라이브 1개 및 22개 입력에서 adapter가 `전체`를 추가해 기대 개수와 불일치(`:1088`, `:1103`), 고정 `tvHomeLiveAll` 클릭 연결 부재(`HomeMainFragmentLoginGuardSourceTest.kt:220`)로 확인되어 요구 동작 미구현에 따른 의도한 RED다. - 2026-08-03: `fragment_v2_main_home.xml`에 예약 폭을 사용하는 고정 `tv_home_live_all` 형제 뷰를 추가하고, `HomeLiveAdapter`는 최대 20개의 라이브 item만 제공하도록 adapter 소유 `전체` 상태·ViewHolder·callback을 제거했다. `HomeMainFragment`는 고정 뷰 터치를 기존 `openHomeOnAirLive()`에 직접 연결했다. 동일 focused test는 GREEN으로 `BUILD SUCCESSFUL`이었고 `--rerun-tasks` 강제 재실행도 성공했다. `mergeDebugResources`, `compileDebugKotlin`, `ktlintCheck`, `git diff --check`도 성공했으며 ktlint 설정 및 Gradle/Kotlin deprecation warning은 기존 경고다. - 2026-08-04: 사용자 확인에 따라 Phase 19의 최종 테스트 범위를 로직/local unit test로 정정했다. 임시 `app/src/androidTest` 계측 테스트, 레이아웃 속성 unit test, 스크린샷·시각 QA 산출물과 QA 전용 에뮬레이터를 제거했다. 앞선 레이아웃 assertion RED 기록은 최종 완료 증거에서 제외하고 adapter 개수 경계와 로그인 가드·라이브 라우팅 로직 테스트만 유지한다. - 2026-08-04: 코드 리뷰 `REV-P19-001`에서 초기 `Loading` 상태가 content를 bind하지 않아 XML 기본 `VISIBLE`인 고정 `전체`가 단독 노출될 수 있음을 확인했다. 저장소 정책상 레이아웃 속성 테스트는 남기지 않고 `ll_home_live_section` 기본값을 `GONE`으로 최소 수정했으며 기존 `bindLiveSection()`의 빈 목록 숨김·비어 있지 않은 목록 표시 로직은 유지했다. - 2026-08-04: focused `HomeMainFragmentLayoutTest`·`HomeMainFragmentLoginGuardSourceTest`, `mergeDebugResources`, `compileDebugKotlin`이 성공했다. 전체 `testDebugUnitTest` 첫 실행은 이번 diff 밖의 기존 `CreatorChannelHomeViewModelTest` 비결정적 assertion 1건이 실패했지만 해당 클래스 격리 실행과 전체 재실행은 성공했다. 전역 `ktlintCheck`는 기존 `audio_content` package underscore 등 이번 diff 밖 위반으로 실패했고 변경된 Kotlin/test 파일은 오류 목록에 없었다. - 2026-08-04: 독립 최종 리뷰에서 adapter item-count 테스트에 남은 미사용 `context`·`RecyclerView` setup 3줄을 제거했다. focused 로직 테스트와 `git diff --check`를 재실행해 성공했고 재리뷰에서 차단 사항 없이 `APPROVE` 판정을 받았다. ### 완료 조건 - [x] 라이브 목록만 가로 스크롤되고 `전체` 항목은 오른쪽 끝에 고정된다. - [x] 고정 `전체` 항목이 라이브 item을 가리지 않고 별도 영역을 차지한다. - [x] 빈 목록은 adapter item 0개이며 라이브 섹션 전체가 숨겨진다. - [x] 라이브 1~20개는 adapter item 수가 입력 개수와 같고, 21개 이상은 정확히 20개다. - [x] 기존 `전체` 디자인·문구·끝 여백·클릭 목적지·로그인 가드와 라이브 item 간격·입장 흐름이 유지된다. - [x] 신규 abstraction·dependency·overlay·커스텀 LayoutManager·레거시 수정·commit이 없다. ### 검증 방법 #### Task R19.1: 초기 로딩 중 고정 `전체` 단독 노출 방지 **Goal 실행 `P19-R1`:** `REV-P19-001`에서 확인한 초기 데이터 바인딩 전 고정 `전체` 단독 노출 가능성을 제거한다. - **시작 조건:** `REV-P19-001` 확정, `HomeRecommendationUiState.Loading`이 content를 bind하지 않는 현재 흐름 확인. - **완료 증거:** `ll_home_live_section` 기본 숨김, 기존 `bindLiveSection()` 표시 정책 유지, resource merge·compile과 Phase 19 로직 회귀 성공. - **범위 밖:** UI/계측 테스트, 레이아웃 속성 unit test, 로딩 상태 구조 변경, 관련 없는 홈 섹션 수정. - **TDD 예외 사유:** 수정 대상이 XML 초기 visibility 속성이며 저장소 정책상 레이아웃 속성 테스트를 작성하지 않는다. 코드 흐름 대조와 resource merge·compile로 검증한다. - [x] `ll_home_live_section`을 XML에서 기본 `GONE`으로 설정한다. - [x] `bindLiveSection()`이 빈 목록은 숨기고 비어 있지 않은 목록은 표시하는 기존 로직을 유지한다. - [x] focused 로직 테스트, resource merge, compile, ktlint와 diff check를 실행해 결과를 기록한다. #### Phase 19 Gate **Goal 실행 `P19-GATE`:** S1~S3의 adapter 경계·라우팅 로직과 resource/compile 계약을 최종 판정한다. - **시작 조건:** `P19-T1`, `P19-R1` 체크박스와 검증 기록 완료. - **완료 증거:** focused·전체 unit test, resource merge, compile, ktlint 결과 분리, diff check와 아래 자동·정적 대조 기록. - **범위 밖:** Gate 통과를 위한 test 삭제·완화, 범위 밖 실패 수정, API·레거시·디자인 변경. 자동·정적 대조: - [x] adapter가 빈 입력 0개, 1~20개 입력 원본 개수, 21개 이상 입력 20개를 반환한다. - [x] adapter에 `전체` item용 view type·ViewHolder·callback이 남지 않는다. - [x] `tv_home_live_all`은 기존 `openHomeOnAirLive()` 로그인 가드 경로를 사용한다. - [x] resource merge와 compile이 성공하고 `app/src/androidTest` 신규 테스트가 없다. - [x] UI/계측 테스트, 기기·에뮬레이터 조작, 스크린샷·시각 QA를 완료 증거로 사용하지 않는다. 중단 조건: S1~S3 로직 회귀, 전체 unit test, resource merge, compile, ktlint, diff check와 리뷰 승인이 끝나면 즉시 중단한다. ## Decision Log | 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 | |---|---|---|---|---|---| | 2026-07-31 | `DEC-017` | 확정 | 모든 `모두 팔로우` 동작은 공통 확인 절차를 사용하고, 현재 구현은 노출 중인 응원 크리에이터 섹션으로 제한한다. 신규 공통 abstraction 없이 기존 `V2ModalDialog`를 재사용한다. | 사용자 `deep-interview`의 B안 선택과 현재 `genreCreators` 숨김 정책 | `P17-T1`, `P17-GATE`, `prd.md`의 `2026-07-31 변경` | | 2026-07-31 | `DEC-018` | 확정 | Phase 7의 2026-06-05 보안 재리뷰에서 승인된 유료 미구매 원본 이미지 미로드 정책을 기준으로 삼고, commit `2d58a876`에서 재도입된 client-side blur 원본 로드를 회귀로 수정한다. | 보안 재리뷰·검증 기록, 현재 `HomePopularCommunityAdapter`와 반대 방향의 test 변경 이력 교차 검토 | `REV-P7-001`, `P7-R1` | | 2026-07-31 | `DEC-019` | 확정 | 두 번째 `Phase 12`와 그 하위 `Task 10.*`는 기존 기록을 보존하면서 `P12B` 계열 고유 별칭을 추가해 이후 리뷰와 회귀 작업에서 사용한다. | `work-plan-docs.md`의 Goal/Task 추적성 규칙과 현재 문서의 중복 식별자 | `REV-P12B-001`, `P12B-R1` | | 2026-07-31 | `DEC-020` | 확정 | 현재 진행 중인 라이브가 0개이면 섹션을 숨기고, 1개 이상이면 최대 20개 라이브 뒤에 `전체` 항목을 항상 표시한다. | 사용자 인터뷰 승인과 기존 `HomeLiveAdapter`·전체 라이브 이동 계약 확인 | `P18-T1`, `P18-GATE`, `prd.md`의 `2026-07-31 변경: 현재 진행 중인 라이브 전체 항목 상시 표시` | | 2026-08-03 | `DEC-021` | 확정 | Phase 18의 `전체` 상시 표시 정책은 유지하되 adapter 마지막 item 배치를 폐기하고, 라이브 목록과 영역을 나누는 오른쪽 고정 형제 뷰로 변경한다. | 사용자 권고안 승인, 현재 수평 RecyclerView·adapter·로그인 가드 흐름 조사 | `P19-T1`, `P19-GATE`, `prd.md`의 `2026-08-03 변경: 현재 진행 중인 라이브 전체 항목 오른쪽 고정` | | 2026-08-04 | `DEC-022` | 확정 | 자동 테스트는 `app/src/test`의 로직/local unit test만 사용하고, 사용자 명시 요청 없이는 UI/계측 테스트·기기/에뮬레이터 조작·스크린샷·시각 QA를 실행하지 않는다. | 사용자 직접 확인과 기존 `code-style.md`의 로직 테스트 전용 원칙 | `AGENTS.md`, 테스트/계획 가이드, `P19-T1`, `P19-R1`, `P19-GATE` | ## Verification Log - 2026-08-04: 사용자 확인에 따라 UI 테스트 없이 로직/local unit test만 수행하는 정책을 `AGENTS.md`, `build-test-style.md`, `code-style.md`, `work-plan-docs.md`, `sample-plan-task.md`에 명시했다. Phase 19의 임시 `androidTest`, 레이아웃 속성 테스트, 스크린샷·시각 QA 산출물과 QA 전용 에뮬레이터를 제거하고 계획·Gate를 adapter 경계와 라우팅 로직 검증 기준으로 정정했다. focused 로직 테스트와 resource merge·compile은 성공했고, 전체 unit test는 기존 비결정적 1건 격리 성공 후 전체 재실행이 성공했다. 전역 ktlint는 이번 diff 밖의 기존 위반으로 실패해 분리 기록했다. - 2026-08-04: Phase 19 최종 독립 리뷰의 유일한 차단 사항이었던 item-count 테스트 미사용 UI setup을 제거했다. focused 로직 테스트와 diff check 성공 후 재리뷰가 `APPROVE`로 종료됐으며 UI/계측 테스트는 추가하거나 재실행하지 않았다. - 2026-08-03: Phase 19 문서 준비로 현재 v2 홈 데이터·adapter·layout·클릭 흐름과 최근 Phase 18 변경을 확인하고, 사용자 승인 설계를 PRD와 `P19-T1`·`P19-GATE`에 반영했다. S1 빈 목록, S2 1~20개 스크롤 고정, S3 21개 이상 cap·기존 라우팅 회귀를 자동·표면 검증 계약으로 정의했다. production·test 구현은 시작하지 않았다. - 2026-07-31: Phase 18 변경 코드 리뷰 및 최신 검증을 수행했다. `HomeLiveAdapter`의 0개·1~20개·21개 이상 경계와 기존 `MoreViewHolder`, 로그인 가드, `HomeOnAirLiveActivity` 이동 경로를 대조해 확정 발견 사항이 없음을 판정했다. focused 74 tests·compile·ktlint와 `--rerun-tasks` 전체 162 suites·1,199 tests가 실패 0·오류 0으로 성공했고 staged·unstaged diff check와 문서 변경 후 `./gradlew tasks --all --console=plain`도 성공했다. 프로젝트 전체 `lintDebug`는 이번 diff 밖의 기존 28 errors·767 warnings로 실패했으며 첫 오류는 `AndroidManifest.xml:316`의 `FacebookActivity` `MissingClass`다. 상세 결과는 `reviews/phase18-live-all-item-review.md`에 기록했다. - 2026-07-31: Phase 18 TDD와 Gate 검증을 완료했다. 구현 전 focused baseline 73개 성공 후 신규 경계 테스트가 라이브 1개에서 `expected:<2> but was:<1>`로 RED 실패했고, `HomeLiveAdapter`의 `hasMore`를 `showAllItem`으로 바꾸고 `items.isNotEmpty()` 조건을 적용한 뒤 focused 74개가 성공했다. `:app:testDebugUnitTest --no-parallel --console=plain` 전체 실행은 162 suites, 1,199 tests, 실패 0·오류 0·skip 0으로 성공했고 `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check`도 성공했다. 기존 deprecation warning 외 실패는 없었으며 신규 파일·dependency·commit은 생성하지 않았다. - 2026-07-31: Phase 18 문서 준비 검증으로 PRD의 0개·1~20개·21개 이상 표시 계약과 Non-Goal을 `P18-T1` 및 `P18-GATE`에 대조했고 누락된 확정 요구사항이 없음을 확인했다. `P18`, `Task 18`, `showAllItem` 추적 검색과 placeholder/TODO 검색, `git diff --check`가 성공했으며 변경 파일은 `prd.md`, `plan-task.md` 두 문서뿐이다. `./gradlew tasks --all --console=plain` 최초 sandbox 실행은 사용자 Gradle cache lock 접근 제한으로 실패했고, 승인된 동일 명령 재실행은 `BUILD SUCCESSFUL`로 완료됐다. production·test 구현은 시작하지 않았고 커밋은 생성하지 않았다. - 2026-07-31: `REV-P7-003` 후속 `Task R7.3` / `P7-R3`를 실행했다. `HomeMainFragmentLayoutTest`의 source 문자열 검사를 제거하고 `HomePopularCommunityAdapterTest`에 실제 adapter/ViewHolder bind 회귀 2건을 추가해 잠금 유료 원본 미로드·기존 Coil 요청 해제·drawable 제거·overlay/가격 유지와 무료·구매 완료 URL 전달을 검증했다. 보안 분기 mutation에서는 잠금 test가 `HomePopularCommunityAdapterTest.kt:72`에서 의도대로 실패했고 원상 복구 후 다시 통과했다. 직접 영향 test·compile·ktlint와 전체 162 suites/1,198 tests가 실패 0·error 0으로 성공했고, 문서 반영 후 `./gradlew tasks --all --console=plain`도 BUILD SUCCESSFUL이었다. 최종 production 코드는 이번 Task에서 추가 변경하지 않았다. Phase별 리뷰를 재검색한 결과 새 확정 항목은 확인되지 않았다. - 2026-07-31: Phase별 리뷰 리포트 최종 문서 검증으로 `git diff --check`를 실행해 출력 없이 성공했고, `reviews/` 아래 Phase 1~17 및 별도 이력 Phase 12B 보고서 18개 존재와 `REV-P7-003` ↔ `Task R7.3` ↔ `P7-R3` 연결을 확인했다. `./gradlew tasks --all --console=plain` 최초 sandbox 실행은 사용자 Gradle cache lock 접근 제한으로 실패했으며, 승인된 동일 명령 재실행은 BUILD SUCCESSFUL로 완료되어 프로젝트 task 인식 문제와 분리했다. - 2026-07-31: 현재 working tree를 Phase별 리뷰 보고서와 다시 대조했다. 신규 확정 항목은 Phase 7의 유료 미구매 원본 이미지 미로드 테스트가 실제 adapter bind, 기존 request/drawable 제거, lock overlay와 가격 유지를 실행하지 않고 production source 문자열만 검사하는 회귀 보호 공백(`REV-P7-003`) 1건이다. 이를 `Task R7.3` / `P7-R3`의 미완료 실행형 회귀 테스트 보강 Task로 Phase 7에 추가했으며 production 코드는 변경하지 않았다. Task 12.2는 사용자 수동 확인 완료 상태로 Phase 5·6·8·9·10·11·12·13 리포트의 오래된 blocker/남은 항목을 현재 상태와 동기화했다. - 2026-07-31: 최신 자동 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*" --tests "kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialogTest" :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck --no-parallel --console=plain`을 실행해 BUILD SUCCESSFUL을 확인했다. 이어 `./gradlew :app:testDebugUnitTest --no-parallel --console=plain`을 실행했고 test result XML 161 suites의 1,197 tests에서 실패 0·error 0으로 BUILD SUCCESSFUL을 확인했다. 과거 `REV-P12-003`의 비결정적 creator channel/preferences 실패 이력은 삭제하지 않고 동일 실패 재현 시 별도 격리 범위로 재개한다. - 2026-07-31: 사용자 수동 확인 결과 Task 12.2 홈 추천 화면 항목과 Phase 17 모두 팔로우 확인창 항목은 모두 동작함을 확인했다. 단, `모두 팔로우` 터치 시 로그인 가드보다 확인 Dialog가 먼저 표시되는 버그가 발견되어 `Task R17.2` / `P17-R2`로 전환했다. - 2026-07-31: `Task R17.2`로 모두 팔로우 확인창 로그인 가드 순서를 복구했다. RED는 source regression 2건 실패로 확인했고, GREEN은 `onCheerFollowAllClick`에서 기존 `ensureV2Access(AccessRequirement.Login)` 통과 후 확인 Dialog를 표시하도록 최소 수정했다. 직접 영향 회귀, compile, ktlint, `git diff --check`는 모두 성공했다. - 2026-07-31: 수동 Gate blocker를 재검증했다. `adb devices`는 `2cec640c34017ece device`를 반환했고 `./gradlew :app:installDebug`, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`는 성공했다. `adb shell dumpsys window`는 `MainV2Activity` foreground를 반환했지만 캡처 `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive_manual_gate_20260731/screen.png`는 검은 배경의 splash/voice on 화면으로 판독되어 홈 추천 화면과 Phase 17 확인창 수동 검증은 계속 blocked다. - 2026-07-31: Phase별 리뷰 보고서와 `Task R12.2` 문서 변경 후 `./gradlew tasks --all --console=plain`을 실행해 BUILD SUCCESSFUL을 확인했다. `reviews/`에는 Phase 1~17 각각의 보고서와 별도 이력 Phase 12B 보고서가 존재하며, 확정 항목의 review ID·신규 Task·검증 기록 연결 및 `git diff --check` 성공을 확인했다. - 2026-07-31: 현재 working tree 최종 회귀로 `./gradlew :app:testDebugUnitTest --no-parallel`을 두 차례 실행했고, 각각 1,196개 중 `CreatorChannelHomeViewModelTest`의 `SharedPreferenceManager.can` assertion 2건과 1건이 실패했다. 실패 class 19개 test를 격리 재실행하면 모두 통과했고, 실패 지점은 비동기 DataStore observer/write를 공유하는 `SharedPreferenceManager.can`이 차감 후 이전 값으로 돌아온 assertion으로 좁혀졌다. 현재 diff에 `v2.creator.channel`, `SharedPreferenceManager`, `AppPreferencesDataStoreProvider` 변경은 없고 홈 추천·모달 focused 159 tests와 resource merge·compile·ktlint는 성공했다. 이 항목은 `REV-P12-003` 보류로 기록하고, 해당 공유 저장소/테스트 격리 범위가 별도로 승인되면 재현 test부터 재개한다. - 2026-07-31: `REV-P12-002` 후속 `Task R12.2`로 미완료 Task 12.2에 남은 `첫 오디오 태그 조건` 표시 계약을 Phase 12B의 제거 결과와 대조했다. 과거 이력은 보존하고 활성 수동 항목만 `제거된 첫 오디오 섹션과 관련 태그 미노출`로 정정했다. production/resource 금지 참조 검색과 `git diff --check`는 성공했으며 production·test·resource는 변경하지 않았다. - 2026-07-31: Task R1.1, R7.2, R12.1, R12B.2, R17.1 후속 보완을 진행했다. R1.1은 현재 `HomeMainFragment` canonical 경로를 PRD에 반영하고 파일 존재/미존재 및 문서 경로 검색을 확인했다. R7.2와 R17.1은 신규 영문 테스트명 3건을 한글 문장으로 정정하고 focused/direct 영향 테스트를 통과시켰다. R12B.2는 PRD 활성 영역의 first audio 완료 조건을 제거하고 과거 기록/제거 기록은 보존했다. R12.1은 연결 기기와 설치·런처 실행은 확인했지만 `SplashActivity` 검은 화면 캡처만 재현되어 Task 12.2 수동 확인은 홈 추천 화면 접근 불가로 blocked 상태를 갱신했다. 검증으로 focused 3개 테스트, `HomeMainFragmentLayoutTest`·`V2ModalDialogTest`, `./gradlew :app:ktlintCheck`, `git diff --check`가 성공했다. - 2026-07-31: Phase 재리뷰 최종 자동 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*" --tests "kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialogTest" :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck --no-parallel`을 실행해 BUILD SUCCESSFUL, 159 tests, 실패 0, error 0을 확인했다. 최초 sandbox 실행은 사용자 Gradle cache lock 접근 제한으로 실패했고, 승인된 동일 명령 재실행으로 코드 결함과 분리했다. `adb devices`에서 사용 가능한 기기 1대를 확인했지만, 이번 리뷰 범위에서 앱 설치나 수동 UI 검증은 실행하지 않았다. - 2026-07-31: Phase별 리뷰 초안을 코드·test·PRD·현재 기기 상태와 재대조했다. 추가 확정 항목은 현재 `HomeMainFragment` 경로와 활성 문서 경로의 불일치(`REV-P1-001`), Phase 7 회귀 테스트의 신규 영문 테스트명(`REV-P7-002`), 연결 기기가 현재 존재하지만 `연결된 Android 기기 없음` 상태로 남은 Phase 12 수동 검증 blocker(`REV-P12-001`), 제거된 first audio가 PRD 활성 영역에 남은 정합성 문제(`REV-P12B-002`), Phase 17 신규 영문 테스트명 2건(`REV-P17-001`)이다. 각각 `Task R1.1`·`Task R7.2`·`Task R12.1`·`Task R12B.2`·`Task R17.1`로 전환했고, 이번 리뷰 요청의 코드 수정 범위가 아니므로 미완료 체크박스로 남겼다. 기존 `REV-P7-001`과 `REV-P12B-001`은 현재 working tree에서 수정·검증까지 완료된 상태로 재판정했다. - 2026-07-31: `plan-task.md` 전체 Phase를 현재 working tree와 대조해 독립 리뷰했다. Phase 7에서 2026-06-05 보안 재리뷰로 제거했던 유료 미구매 원본 이미지 로드가 commit `2d58a876`에서 client-side blur 방식으로 재도입된 회귀를 `REV-P7-001`로, 두 번째 `Phase 12` 및 그 안의 `Task 10.1`·`Task 10.2` 중복 식별자를 `REV-P12B-001`로 확정해 각각 `Task R7.1`/`P7-R1`, `Task R12B.1`/`P12B-R1`로 전환했다. 그 밖의 Phase에는 확정 코드 결함이 없었고, Phase 12 최종 수동 확인과 Phase 17 수동 Gate는 기존 blocker를 유지한다. 직접 영향 검증 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*" --tests "kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialogTest" :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck --no-parallel`은 159 tests, 실패 0으로 성공했다. 전체 `./gradlew :app:testDebugUnitTest --no-parallel` 최초 실행은 범위 밖 `CreatorChannelHomeViewModelTest`의 비동기 `SharedPreferenceManager.can` assertion 1건이 실패했으나 해당 클래스 격리 재실행과 전체 1,196 tests 재실행은 모두 성공해 이번 Phase 변경 회귀와 분리했다. Phase별 결과는 `reviews/phase1-*.md`부터 `reviews/phase17-*.md`까지 기록했으며 기존 Phase 16 보고서는 재검토 기록을 누적했다. - 2026-07-31: Phase 17 독립 리뷰 게이트를 수행했다. 리뷰 결과는 PASS이며 blocking finding은 없었다. 리뷰에서 `onCheerFollowAllClick`이 확인창 helper만 호출하는 점, 확인 버튼에서만 `followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds)`가 실행되는 점, 취소는 `V2ModalDialog`의 null cancel callback으로 dismiss만 수행하는 점, `HomeFollowAllButtonBinder`의 완료/빈 목록 no-op이 유지되는 점, 한국어·영어·일본어 문자열이 PRD와 일치하는 점, 수동 시각 검증 blocker가 문서에 정직하게 남은 점을 확인했다. - 2026-07-31: Phase 17 RED/GREEN 검증을 수행했다. RED는 `HomeMainFragmentLayoutTest`에 확인창 연결과 다국어 문자열 계약을 먼저 추가한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`에서 신규 `home_recommendation_follow_all_confirm_*` resource 미정의 컴파일 실패로 확인했다. GREEN 구현 후 동일 focused test는 BUILD SUCCESSFUL을 확인했다. 이후 직접 영향 회귀로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialogTest"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`를 실행했고 모두 성공했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고다. 표면 확인으로 `adb devices`에서 `2cec640c34017ece device`를 확인했고 `./gradlew :app:installDebug`, `adb shell monkey -p kr.co.vividnext.sodalive.debug -c android.intent.category.LAUNCHER 1`, `adb shell screencap`/`adb pull`을 실행했다. 설치와 런처 실행은 성공했지만 `/var/folders/yh/8xsbvpsj5wg2qnxzxdp11_gm0000gn/T/opencode/sodalive_phase17_home.png` 캡처는 완전 검은 화면으로 확인되어 실제 확인창 육안 확인은 blocked로 남긴다. 커밋은 생성하지 않았다. - 2026-07-31: Task R7.1 RED/GREEN을 진행했다. RED는 `HomeMainFragmentLayoutTest.home popular community adapter does not load original image for locked paid post`를 추가한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest.home popular community adapter does not load original image for locked paid post"`에서 `HomeMainFragmentLayoutTest.kt:1032` assertion 실패로 확인했다. GREEN은 `HomePopularCommunityAdapter`에서 유료 미구매 item의 `imageUrl`을 image loader에 전달하지 않도록 바꾸고 `BlurTransformation` client-side blur 경로를 제거한 뒤 동일 focused test BUILD SUCCESSFUL을 확인했다. PRD의 blur 문구는 원본 미로드 보안 결정과 일치하도록 갱신했다. 남은 검증은 `HomeMainFragmentLayoutTest` 전체, compile, ktlint, `git diff --check`, 리뷰 보고서 후속 기록이다. - 2026-07-31: Task R7.1 최종 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`를 순차 실행했고 모두 성공했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고다. `reviews/phase7-popular-community-review.md` 수정 후 검증 기록까지 누적해 `REV-P7-001` 후속 항목을 완료했다. - 2026-07-31: Task R12B.1 문서 추적성 복구를 완료했다. 중복 재현 명령 `rg -n "^(### Phase 12:|## Phase 12:|#### Task 10\.|- \[x\] \*\*Task 10\.)" "docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md"`로 첫 번째 `Phase 12`, 두 번째 `Phase 12`, Phase 10 `Task 10.1`, 두 번째 Phase 12의 `Task 10.1`·`Task 10.2` 충돌을 확인했다. 기존 완료 제목과 체크박스는 보존하고 두 번째 Phase 12에 `P12B`, `P12B-T1`, `P12B-T2` 별칭 표와 이후 참조 규칙을 추가했다. `rg -n "P12B|P12B-T1|P12B-T2|P12B-R1|REV-P12B-001" "docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md" "docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-first-audio-removal-review.md"`와 `git diff --check`는 성공했고, 리뷰 보고서 후속 기록까지 누적해 `REV-P12B-001` 후속 항목을 완료했다. - 2026-07-31: 최종 전체 회귀로 `./gradlew :app:testDebugUnitTest`를 실행했고 BUILD SUCCESSFUL을 확인했다. Gradle deprecation warning은 기존 경고다. - 2026-07-31: Phase 17 문서 준비 검증으로 PRD의 공통 정책·현재 적용 범위·다국어 문구·Non-Goal을 `P17-T1` 및 `P17-GATE`에 대조했고 누락된 확정 요구사항이 없음을 확인했다. 신규 Phase의 구현·Gate 체크박스는 모두 미완료이며 production·test·resource 구현을 시작하지 않았다. `git status --short`는 변경 파일이 `prd.md`, `plan-task.md` 두 문서뿐임을 표시했고, placeholder 검색과 `git diff --check`는 출력 없이 성공했다. `./gradlew tasks --all`은 최초 sandbox 실행에서 사용자 Gradle cache lock 접근 제한으로 실패했으나 승인된 동일 명령 재실행은 `BUILD SUCCESSFUL`로 완료됐다. - 2026-07-30: Phase 16 방금 활동한 크리에이터 LIVE 라우팅 검증으로 focused RED/GREEN과 직접 영향 회귀를 수행했다. RED는 `creatorId`, `LiveRoom`, `CreatorChannel` 미정의 컴파일 실패로 확인했고, GREEN 후 focused home tests와 `v2.live.action.*`, `v2.creator.action.*` 회귀, `compileDebugKotlin`, `ktlintCheck`, `git diff --check`가 모두 통과했다. 리뷰에서 `targetId <= 0L`일 때 creator fallback이 실행될 수 있는 차단 이슈가 확인되어 `0L/-1L` 회귀 테스트와 null 전용 fallback 분기를 추가했고, 재검증과 재리뷰에서 blocking finding 없음으로 승인받았다. 수동 표면 확인으로 `adb devices`에서 `2cec640c34017ece device`를 확인하고 `installDebug` 및 launcher monkey 실행을 성공시켰다. 실제 LIVE item 터치는 추천 데이터 조건이 필요해 자동 route/source 테스트로 대체했다. - 2026-07-12: Phase 15 추천 섹션 타이틀 chevron 숨김 변경 검증으로 `HomeMainFragmentSourceTest`에 `홈 추천 최근 데뷔와 AI 캐릭터 섹션 타이틀은 chevron을 숨긴다`를 추가했고, 구현 전 `HomeMainFragmentSourceTest.kt:40` assertion 실패로 RED를 확인했다. `HomeMainFragment.setUpSectionTitles()`에서 `viewHomeRecentDebutTitle`, `viewHomeAiCharacterTitle`의 `showMore = true`를 제거한 뒤 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"`, `./gradlew :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck`, `git diff --check`가 통과했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation warning은 기존 설정 경고다. `./gradlew :app:testDebugUnitTest` 전체 실행은 `CreatorChannelActivitySourceTest > 팬Talk 섹션은 Figma ListComment layout과 bind로 렌더링한다`, `CreatorChannelHomeViewModelTest > 채널 후원 성공은 기존 후원 API를 호출하고 홈을 다시 로드한다` 실패 후 타임아웃됐으며, 실패 영역은 이번 변경 파일과 무관한 `v2.creator.channel` 범위다. `adb devices`는 처음 1대를 표시했으나 `:app:installDebug` 시점에는 `No connected devices!`로 실패했고 이후 `adb devices`가 빈 목록을 반환해 실기기 수동 확인은 수행하지 못했다. - 2026-07-12: `genreCreators` UI 미표시 변경 검증으로 targeted RED/GREEN 테스트를 수행했다. 리뷰에서 PRD 앞부분의 장르별 크리에이터 표시/모두 팔로우 문구와 `HomeRecommendationUiState.Content.isEmpty`의 `genreCreators.groups` 포함이 차단 이슈로 확인되어, PRD 문구를 미표시 정책으로 정리하고 `isEmpty`는 실제 표시 섹션만 기준으로 판단하도록 수정했다. `home recommendation empty state ignores hidden genre creators` 테스트는 수정 전 `HomeMainFragmentLayoutTest.kt:180` assertion 실패로 RED를 확인했고, 수정 후 GREEN 전환을 확인했다. 추가 재검토에서 plan-task 과거 Task 6.4/6.12와 코드/테스트에 남은 `genreCreators` follow-all 및 pager UI 활성 계약이 차단 이슈로 확인되어, 과거 Task 문구는 현재 미사용 정책으로 정리하고 `HomeMainFragment`의 genre follow-all 연결, `HomeRecommendationViewModel`의 genre follow 완료 상태 갱신, 활성 UI/follow-all 테스트 계약을 제거했다. 이후 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck`를 실행해 BUILD SUCCESSFUL을 확인했다. `ktlintCheck` 최초 재실행은 테스트 파일 불필요한 빈 줄로 실패했고 해당 줄만 제거한 뒤 재실행해 성공했다. `git diff --check` 출력 없음도 확인했다. `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐고 lint 실패는 없었다. `adb devices` 결과 연결된 기기/에뮬레이터가 없어 실기기 수동 확인은 수행하지 못했다. - 2026-07-10: Phase 13 코드 리뷰 및 검증을 재수행했다. 리뷰 범위는 최근 활동 COMMUNITY route가 `targetId`를 `postId`로 해석해 `CreatorChannelCommunityDetailActivity.newIntent(context, postId)`를 생성하는지, 인기 커뮤니티 item이 `FeedItem.Community.postId` 기반으로 상세 Activity를 여는지, 기존 `CreatorCommunityAllActivity`/커뮤니티 목록 extra 제거 여부, 로그인 가드 전 invalid return 유지 여부다. 코드 리뷰 결과 blocking finding은 없었다. 검증으로 Phase 13 targeted test, `HomeMainFragmentLayoutTest` 전체, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --cached --check`가 모두 통과했고, `rg`로 v2 home production 코드에 기존 커뮤니티 목록 라우팅 참조가 남지 않았음을 확인했다. - 2026-07-10: 커뮤니티 상세 라우팅 변경 회귀 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest"`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check`를 실행했고 모두 통과했다. `ktlintCheck` 최초 실행은 이번에 수정한 source test 파일의 기존 긴 assertion 줄 때문에 실패했으며, 동작 변경 없이 줄바꿈과 긴 문자열 분리만 적용한 뒤 재실행해 BUILD SUCCESSFUL을 확인했다. `rg`로 v2 home production 코드에 `CreatorCommunityAllActivity`와 기존 커뮤니티 전체목록 extra 참조가 남지 않았고, 해당 문자열은 금지 조건 source test에만 남아 있음을 확인했다. - 2026-06-29: `처음부터 함께 성장!` 제거 회귀 검증으로 `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*" --tests "kr.co.vividnext.sodalive.v2.main.content.overview.*" :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck`를 실행했고 BUILD SUCCESSFUL을 확인했다. `git diff --check` 출력 없음도 확인했다. Android 기기/에뮬레이터 접근이 없어 실제 화면 조작은 수행하지 못했으며, `rg`로 `app/src/main`의 first audio/FIRST_AUDIO_CONTENT production 참조 제거를 확인했다. 리뷰 게이트에서 차단 이슈 없이 승인받았다.