From f600d10e8b63b6fdc812b18a874b077dfe072ca8 Mon Sep 17 00:00:00 2001 From: klaus Date: Thu, 30 Jul 2026 16:42:33 +0900 Subject: [PATCH] =?UTF-8?q?feat(home):=20=EC=B5=9C=EA=B7=BC=20=ED=99=9C?= =?UTF-8?q?=EB=8F=99=20LIVE=20=EB=9D=BC=EC=9A=B0=ED=8C=85=EC=9D=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sodalive/v2/main/home/HomeMainFragment.kt | 4 + .../home/data/HomeRecommendationModels.kt | 1 + .../home/model/HomeRecommendationMappers.kt | 1 + .../home/model/HomeRecommendationUiModels.kt | 19 ++- .../main/home/HomeMainFragmentLayoutTest.kt | 46 +++++- .../HomeMainFragmentLoginGuardSourceTest.kt | 14 ++ .../plan-task.md | 99 ++++++++++++ .../prd.md | 36 +++++ .../phase16-recent-activity-live-routing-review.md | 147 ++++++++++++++++++ docs/agent-guides/build-test-style.md | 7 + 10 files changed, 366 insertions(+), 8 deletions(-) create mode 100644 docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase16-recent-activity-live-routing-review.md diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt index 014aef85..428fa50d 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt @@ -707,6 +707,10 @@ class HomeMainFragment : BaseFragment( private fun onRecentActivityClick(item: HomeRecommendationRecentlyActiveCreatorUiModel) { val route = item.toHomeRecommendationRecentlyActiveCreatorRoute() ?: return when (route) { + is HomeRecommendationRecentlyActiveCreatorRoute.LiveRoom -> liveActionCoordinator.enterLiveRoom(route.roomId) + is HomeRecommendationRecentlyActiveCreatorRoute.CreatorChannel -> handleCreatorAction( + CreatorActionCommand.Profile(route.creatorId) + ) is HomeRecommendationRecentlyActiveCreatorRoute.AudioContent -> handleContentAction( ContentActionCommand.AudioDetail(audioContentId = route.contentId) ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt index 6b71ab0a..e9c9ed19 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeRecommendationModels.kt @@ -34,6 +34,7 @@ data class HomeBannerItem( @Keep data class HomeActiveCreatorItem( + @SerializedName("creatorId") val creatorId: Long, @SerializedName("creatorNickname") val creatorNickname: String, @SerializedName("creatorProfileImage") val creatorProfileImage: String, @SerializedName("activityType") val activityType: String, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt index 71847e0d..2d001ff0 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationMappers.kt @@ -44,6 +44,7 @@ fun HomeBannerItem.toUiModel(): HomeRecommendationBannerUiModel = HomeRecommenda fun HomeActiveCreatorItem.toUiModel(): HomeRecommendationRecentlyActiveCreatorUiModel { val creatorActivityType = CreatorActivityType.from(activityType) return HomeRecommendationRecentlyActiveCreatorUiModel( + creatorId = creatorId, nickname = creatorNickname, profileImage = creatorProfileImage, activityType = creatorActivityType, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt index 9047bca2..b1d51a49 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeRecommendationUiModels.kt @@ -116,6 +116,7 @@ fun HomeRecommendationBannerRoute.toHomeRecommendationBannerIntent(context: Cont private fun homeRecommendationAppLinkHost(): String = "${BuildConfig.APPSCHEME}.onelink.me" data class HomeRecommendationRecentlyActiveCreatorUiModel( + val creatorId: Long, val nickname: String, val profileImage: String, val activityType: CreatorActivityType?, @@ -125,6 +126,10 @@ data class HomeRecommendationRecentlyActiveCreatorUiModel( ) sealed interface HomeRecommendationRecentlyActiveCreatorRoute { + data class LiveRoom(val roomId: Long) : HomeRecommendationRecentlyActiveCreatorRoute + + data class CreatorChannel(val creatorId: Long) : HomeRecommendationRecentlyActiveCreatorRoute + data class AudioContent(val contentId: Long) : HomeRecommendationRecentlyActiveCreatorRoute data class Community(val postId: Long) : HomeRecommendationRecentlyActiveCreatorRoute @@ -132,12 +137,18 @@ sealed interface HomeRecommendationRecentlyActiveCreatorRoute { fun HomeRecommendationRecentlyActiveCreatorUiModel.toHomeRecommendationRecentlyActiveCreatorRoute(): HomeRecommendationRecentlyActiveCreatorRoute? { - val routeTargetId = targetId?.takeIf { it > 0 } ?: return null return when (activityType) { - CreatorActivityType.Live -> null + CreatorActivityType.Live -> when { + targetId == null -> creatorId.takeIf { it > 0L } + ?.let(HomeRecommendationRecentlyActiveCreatorRoute::CreatorChannel) + targetId > 0L -> HomeRecommendationRecentlyActiveCreatorRoute.LiveRoom(targetId) + else -> null + } CreatorActivityType.LiveReplay, - CreatorActivityType.Audio -> HomeRecommendationRecentlyActiveCreatorRoute.AudioContent(routeTargetId) - CreatorActivityType.Community -> HomeRecommendationRecentlyActiveCreatorRoute.Community(routeTargetId) + CreatorActivityType.Audio -> targetId?.takeIf { it > 0L } + ?.let(HomeRecommendationRecentlyActiveCreatorRoute::AudioContent) + CreatorActivityType.Community -> targetId?.takeIf { it > 0L } + ?.let(HomeRecommendationRecentlyActiveCreatorRoute::Community) null -> null } } diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt index 219b86ec..eaebc777 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt @@ -720,6 +720,7 @@ class HomeMainFragmentLayoutTest { link = "https://example.com" ).toUiModel() val activeCreator = HomeActiveCreatorItem( + creatorId = 45L, creatorNickname = "활동 크리에이터", creatorProfileImage = "https://example.com/active.png", activityType = "AUDIO", @@ -749,6 +750,7 @@ class HomeMainFragmentLayoutTest { assertEquals(30L, banner.creatorId) assertEquals(40L, banner.seriesId) assertEquals("https://example.com", banner.link) + assertEquals(45L, activeCreator.creatorId) assertEquals("활동 크리에이터", activeCreator.nickname) assertEquals("https://example.com/active.png", activeCreator.profileImage) assertEquals("방금 전", activeCreator.activityAt) @@ -904,6 +906,11 @@ class HomeMainFragmentLayoutTest { @Test fun `home recent activity route maps activity type to target destination`() { + assertEquals( + HomeRecommendationRecentlyActiveCreatorRoute.LiveRoom(10L), + recentActivityCreator(CreatorActivityType.Live, targetId = 10L) + .toHomeRecommendationRecentlyActiveCreatorRoute() + ) assertEquals( HomeRecommendationRecentlyActiveCreatorRoute.AudioContent(11L), recentActivityCreator(CreatorActivityType.LiveReplay, targetId = 11L) @@ -922,12 +929,41 @@ class HomeMainFragmentLayoutTest { } @Test - fun `home recent activity route ignores live unknown and missing target`() { + fun `홈 최근 활동 LIVE는 target 유무에 따라 라이브 룸 또는 크리에이터 채널로 이동한다`() { assertEquals( - null, - recentActivityCreator(CreatorActivityType.Live, targetId = 10L) + HomeRecommendationRecentlyActiveCreatorRoute.LiveRoom(10L), + recentActivityCreator(CreatorActivityType.Live, targetId = 10L, creatorId = 20L) .toHomeRecommendationRecentlyActiveCreatorRoute() ) + assertEquals( + HomeRecommendationRecentlyActiveCreatorRoute.CreatorChannel(20L), + recentActivityCreator(CreatorActivityType.Live, targetId = null, creatorId = 20L) + .toHomeRecommendationRecentlyActiveCreatorRoute() + ) + assertEquals( + null, + recentActivityCreator(CreatorActivityType.Live, targetId = 0L, creatorId = 20L) + .toHomeRecommendationRecentlyActiveCreatorRoute() + ) + assertEquals( + null, + recentActivityCreator(CreatorActivityType.Live, targetId = -1L, creatorId = 20L) + .toHomeRecommendationRecentlyActiveCreatorRoute() + ) + assertEquals( + null, + recentActivityCreator(CreatorActivityType.Live, targetId = null, creatorId = 0L) + .toHomeRecommendationRecentlyActiveCreatorRoute() + ) + assertEquals( + null, + recentActivityCreator(CreatorActivityType.Live, targetId = null, creatorId = -1L) + .toHomeRecommendationRecentlyActiveCreatorRoute() + ) + } + + @Test + fun `홈 최근 활동 route는 알 수 없는 activity와 유효하지 않은 target을 무시한다`() { assertEquals( null, recentActivityCreator(activityType = null, targetId = 10L).toHomeRecommendationRecentlyActiveCreatorRoute() @@ -1393,9 +1429,11 @@ class HomeMainFragmentLayoutTest { private fun recentActivityCreator( activityType: CreatorActivityType?, - targetId: Long? + targetId: Long?, + creatorId: Long = 20L ): HomeRecommendationRecentlyActiveCreatorUiModel { return HomeRecommendationRecentlyActiveCreatorUiModel( + creatorId = creatorId, nickname = "크리에이터", profileImage = "", activityType = activityType, diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt index 1a88be38..b94c1391 100644 --- a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt @@ -125,6 +125,20 @@ class HomeMainFragmentLoginGuardSourceTest { assertTrue(source.contains("private val liveActionCoordinator: LiveActionCoordinator by lazy")) } + @Test + fun `HomeMainFragment 최근 활동 LIVE route는 공통 Live와 Creator Action을 사용한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt" + ).readText() + + val clickSource = source.substringFrom( + "private fun onRecentActivityClick(item: HomeRecommendationRecentlyActiveCreatorUiModel)" + ) + assertTrue(clickSource.contains("liveActionCoordinator.enterLiveRoom(route.roomId)")) + assertTrue(clickSource.contains("handleCreatorAction(")) + assertTrue(clickSource.contains("CreatorActionCommand.Profile(route.creatorId)")) + } + @Test fun `HomeMainFragment loading dialog는 통합 loading state로 제어한다`() { val source = projectFile( diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md b/docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md index 7c96fded..2a830f4b 100644 --- a/docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md @@ -793,7 +793,106 @@ - 검증 기록: - 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 계약으로 대체했다. 커밋은 생성하지 않았다. + ## Verification Log +- 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 코드에 기존 커뮤니티 목록 라우팅 참조가 남지 않았음을 확인했다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/prd.md b/docs/20260601_메인_홈_추천_UI와_API_연동/prd.md index e7599bab..82c47990 100644 --- a/docs/20260601_메인_홈_추천_UI와_API_연동/prd.md +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/prd.md @@ -88,6 +88,15 @@ data class HomeRecommendationResponse( #### Changed Item Contracts ```kotlin +data class HomeActiveCreatorItem( + val creatorId: Long, + val creatorNickname: String, + val creatorProfileImage: String, + val activityType: String, + val activityAt: String, + val targetId: Long? +) + data class HomeAiCharacterItem( val characterId: Long, val creatorId: Long, @@ -115,6 +124,9 @@ data class HomePopularCommunityPostItem( ``` #### Changed Item Requirements +- `HomeActiveCreatorItem.creatorId`는 서버가 항상 양수로 제공하는 필수 `Long` 필드이며, 최근 활동 UI model까지 보존한다. +- API 계약과 별개로 클라이언트는 크리에이터 채널 route 생성 전에 `creatorId > 0L`을 확인한다. +- `HomeActiveCreatorItem.activityType == LIVE`일 때 유효한 `targetId > 0L`는 라이브 `roomId`로 해석하고, `targetId == null && creatorId > 0L`이면 `creatorId`를 크리에이터 채널 이동 값으로 사용한다. - `HomeAiCharacterItem.creatorId`는 `크리에이터와 이야기를 나눠요!` 섹션 item 터치 시 크리에이터 채널 이동 값으로 사용한다. - `HomeAiCharacterItem.characterId`는 `CharacterChatThumbnailView` 표시 모델의 캐릭터 식별 값으로 유지하되, 추천 홈 item 터치 목적지로는 사용하지 않는다. - `HomeAiCharacterItem.profileImage`는 `CharacterChatThumbnailView`의 실제 이미지 URL로 사용한다. @@ -144,6 +156,18 @@ enum class RecommendedActivityType(val code: String) { - 알 수 없는 code는 크래시시키지 않고 기본값 또는 미표시 정책을 구현 계획에서 확정한다. - mapper 또는 enum 변환 로직은 local unit test로 검증한다. +### 방금 활동한 크리에이터 LIVE 라우팅 + +#### Requirements +- `activityType == LIVE && targetId > 0L`인 item을 터치하면 `targetId`를 `roomId`로 사용해 라이브 룸 진입을 요청한다. +- `activityType == LIVE && targetId == null`인 item을 터치하면 `creatorId > 0L`을 확인한 뒤 크리에이터 채널로 이동한다. +- 서버 버그 등으로 `creatorId <= 0L`이면 route를 만들지 않고 이동하지 않는다. +- 라이브 룸 진입은 홈 전용 인증 분기를 추가하지 않고 기존 `LiveActionCoordinator.enterLiveRoom(roomId)`을 사용한다. +- `LiveActionCoordinator`의 기존 흐름에 따라 비로그인 사용자는 로그인 절차로 이동하고, 방 상세 조회 후 성인 방이면 사용자 상태에 맞춰 본인인증 또는 콘텐츠 보기 옵션 설정 절차를 적용한다. +- 크리에이터 채널 이동은 기존 `CreatorActionCommand.Profile(creatorId)` 공통 액션을 사용한다. +- `LIVE_REPLAY`, `AUDIO`, `COMMUNITY`, 알 수 없는 activity type의 기존 라우팅 정책은 변경하지 않는다. +- 이번 작업에서는 공통 로그인·본인인증·성인 콘텐츠 보기 정책 자체를 변경하지 않는다. + ### 사업자 정보 접기/더보기 Figma 최하단 사업자 정보 섹션(`218:2058`)은 외부 라이브러리 없이 TextView와 클릭 가능한 컨트롤로 구현한다. @@ -358,6 +382,9 @@ HomeRecommendation 화면 - `FeedCommunityView`에서 keyword 영역이 추천 페이지에 노출되지 않고, 이미지와 유료 미구매 overlay가 표시된다. - `AudioContentCardView`에서 `ic_content_tag_point`, `무료`, `FIRST` 태그가 조건에 맞게 표시되고, `ic_content_tag_original`은 표시되지 않는다. - `HomeAiCharacterItem.creatorId`가 UI model까지 보존되고, AI 캐릭터 item 터치 시 `CreatorChannelActivity`에 `creatorId`가 전달된다. +- `HomeActiveCreatorItem.creatorId`가 UI model까지 보존되고, `LIVE` item은 `targetId` 유무에 따라 라이브 룸 또는 크리에이터 채널 route로 분기된다. +- 최근 활동 `LIVE`의 `targetId == null && creatorId <= 0L` 입력에서 route가 생성되지 않는다. +- 최근 활동 `LIVE`의 라이브 룸 진입이 `LiveActionCoordinator`를 사용해 로그인·본인인증·성인 콘텐츠 보기 설정 정책을 우회하지 않는다. - 제외 대상인 `추천 필모그래피`, `또 다른 모습` 섹션이 화면에 생성되지 않는다. - 최근 응원이 많은 크리에이터 모두 팔로우 API success 후 버튼이 `모두 팔로우 완료`와 `ic_new_following` 상태로 변경된다. - 사업자 정보는 기본 3줄 말줄임, 더보기 후 전체 표시, 접기 후 3줄 복귀가 동작한다. @@ -377,6 +404,7 @@ HomeRecommendation 화면 --- ## 12. 검증 기록 +- 2026-07-30: 사용자 인터뷰에서 `HomeActiveCreatorItem.creatorId`는 서버가 항상 양수로 제공하는 필수 `Long` 필드로 확정했다. 현재 `CreatorActivityType.Live` route가 명시적으로 `null`을 반환해 터치 액션이 종료되는 것을 코드로 확인했으며, `targetId != null`이면 기존 `LiveActionCoordinator.enterLiveRoom(targetId)`, `targetId == null && creatorId > 0L`이면 기존 `CreatorActionCommand.Profile(creatorId)`를 사용하도록 요구사항을 확정했다. 서버 버그 등으로 `creatorId <= 0L`이면 이동하지 않는 방어 검증을 포함한다. 공통 라이브 진입을 재사용해 비로그인·본인인증·성인 콘텐츠 보기 설정 정책을 그대로 적용하고, 이번 범위에서는 해당 공통 정책 자체를 변경하지 않는다. PRD 변경 후 placeholder·모순·모호성·범위를 자체 검토하고 `git diff --check`와 `./gradlew tasks --all`을 실행했으며 모두 성공했다. - 2026-07-10: 사용자 요청에 따라 `방금 활동한 크리에이터` 섹션의 `activityType == COMMUNITY` 클릭과 `인기 커뮤니티` 섹션 item 클릭을 커뮤니티 전체 목록이 아니라 `CreatorChannelCommunityDetailActivity`로 이동하도록 요구사항과 구현을 반영했다. 최근 활동 COMMUNITY의 `targetId`는 커뮤니티 게시글 `postId`로 해석하고, 인기 커뮤니티는 `HomePopularCommunityPostItem.postId`를 상세 화면 식별 값으로 사용한다. - 2026-06-29: 사용자 추가 요청에 따라 `최근 데뷔한 크리에이터` item을 기존 `205dp x 259dp` 비율로 축소한 `185dp x 234dp`로 표시하고, 이미지 단독이 아니라 item root 전체에 `14dp` rounded clipping을 적용해야 한다는 요구사항을 PRD에 반영했다. Figma `24:5534`, `24:5537` design context와 screenshot을 확인했다. - 2026-06-23: 기존 `CreatorChannelFanTalkFragmentLayoutTest.kt:78` ktlint 줄바꿈 위반을 동작 변경 없이 수정한 뒤 `./gradlew :app:testDebugUnitTest`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:ktlintCheck`가 모두 BUILD SUCCESSFUL임을 재확인했다. `adb devices` 결과 연결된 Android 기기가 없어 실제 기기 수동 확인은 수행하지 못했고, 해당 수동 확인은 환경 차단으로 계획 문서에 남겼다. @@ -431,3 +459,11 @@ HomeRecommendation 화면 - `최근 데뷔한 크리에이터` 섹션 타이틀 우측 chevron을 표시하지 않는다. - `크리에이터와 이야기를 나눠요!` 섹션 타이틀 우측 chevron을 표시하지 않는다. - 섹션 목록 표시, item 터치 동작, 다른 섹션의 chevron 정책은 변경하지 않는다. + +### 2026-07-30 변경: 최근 활동 LIVE 라우팅 +- 홈 추천 API의 `HomeActiveCreatorItem`에 서버가 항상 양수로 제공하는 필수 `creatorId: Long`을 추가한다. +- `activityType == LIVE && targetId > 0L`이면 `targetId`를 `roomId`로 사용해 기존 공통 라이브 진입 흐름을 실행한다. +- `activityType == LIVE && targetId == null`이면 `creatorId > 0L`을 확인한 뒤 크리에이터 채널로 이동한다. +- 서버 버그 등으로 `creatorId <= 0L`이면 route를 만들지 않고 이동하지 않는다. +- 공통 라이브 진입 흐름을 재사용해 비로그인, 본인인증 필요, 성인 콘텐츠 보기 설정 필요 상태를 기존 접근 정책으로 처리한다. +- 다른 activity type의 기존 라우팅과 공통 접근 정책 자체는 변경하지 않는다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase16-recent-activity-live-routing-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase16-recent-activity-live-routing-review.md new file mode 100644 index 00000000..d7e55dd1 --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase16-recent-activity-live-routing-review.md @@ -0,0 +1,147 @@ +# Phase 16 방금 활동한 크리에이터 LIVE 라우팅 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 16 / Task 16.1 / Phase 16 Gate | +| 기준 commit 또는 working tree | `a1aa9c224326e4f730f715a4eba20add33329aed` + Phase 16 미커밋 working tree | +| 리뷰 일자 | 2026-07-30 | +| 리뷰어 | Codex | +| 기준 문서 | `docs/20260601_메인_홈_추천_UI와_API_연동/prd.md`, `docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md`, `docs/agent-guides/build-test-style.md` | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +### 목적 + +- Phase 16의 DTO, mapper, route, Fragment 액션 연결이 PRD의 최근 활동 `LIVE` 라우팅 요구사항을 충족하는지 확인한다. +- 완료 체크박스와 실제 코드, 테스트, 검증 기록이 일치하는지 확인한다. +- 기존 공통 라이브·크리에이터 액션을 우회하거나 다른 activity type을 회귀시키지 않는지 확인한다. + +### 포함 범위 + +- 코드: + - `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/HomeMainFragment.kt` +- 테스트: + - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLayoutTest.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragmentLoginGuardSourceTest.kt` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/live/action/` + - `app/src/test/java/kr/co/vividnext/sodalive/v2/creator/action/` +- 문서: PRD의 `방금 활동한 크리에이터 LIVE 라우팅`, `plan-task.md`의 Phase 16, Phase 16 focused test guide. +- 수동 대조: DTO → UI model → route → 공통 액션 호출 경로와 working tree 변경 범위. + +### 제외 범위 + +- `LiveActionCoordinator`, `CreatorAction`, `V2AccessGuard` 자체 정책의 변경 또는 재설계. +- 실제 서버 추천 데이터와 로그인·본인인증 상태를 이용한 기기 터치 검증. +- Phase 16 변경 경로 밖에서 관찰된 `v2.creator.channel` 테스트 안정성 수정. +- 다른 Phase 기능의 재리뷰. + +## 3. 판정 기준 + +### 심각도 + +| 심각도 | 기준 | +|---|---| +| Blocker | 보안·데이터 손실 위험, 핵심 흐름 불능, 완료 판정을 무효화하는 문제 | +| High | 확정 요구사항·API Contract 위반 또는 주요 회귀 | +| Medium | 제한된 조건에서 발생하는 기능·접근성·복구 문제 | +| Low | 유지보수성, 문서 정합성 또는 비핵심 UX 문제 | + +### 상태 + +| 상태 | 의미 | 후속 처리 | +|---|---|---| +| 후보 | 근거를 발견했지만 아직 재현·판정하지 않음 | 검증 후 상태 변경 | +| 확정 | 코드·test·문서 근거로 문제가 확인됨 | `plan-task.md` 회귀 수정 Task 후보 | +| 오탐 | 요구사항이나 실행 결과상 문제가 아님 | 근거를 남기고 종료 | +| 보류 | 외부 계약·환경·제품 결정이 필요함 | 담당 주체와 재개 조건 기록 | +| 수정 완료 | 수정과 관련 검증이 완료됨 | 실행 명령과 결과 연결 | + +## 4. 검토한 근거 + +### 문서와 코드 + +- 요구사항: + - `prd.md:159`의 `방금 활동한 크리에이터 LIVE 라우팅` + - `plan-task.md:796`의 Phase 16 +- DTO·mapper: + - `HomeRecommendationModels.kt:36`에서 필수 `creatorId: Long` 계약 확인. + - `HomeRecommendationMappers.kt:44`에서 DTO의 `creatorId`가 UI model로 보존됨을 확인. +- route: + - `HomeRecommendationUiModels.kt:128`에서 `LiveRoom`, `CreatorChannel` route 정의 확인. + - `HomeRecommendationUiModels.kt:138`에서 `targetId > 0L`, `targetId == null && creatorId > 0L`, invalid ID no-op 분기 확인. +- Fragment: + - `HomeMainFragment.kt:707`에서 `LiveRoom`은 `LiveActionCoordinator.enterLiveRoom()`, `CreatorChannel`은 `CreatorActionCommand.Profile`로 전달됨을 확인. +- 테스트: + - `HomeMainFragmentLayoutTest.kt:907`에서 다른 activity type route 회귀 확인. + - `HomeMainFragmentLayoutTest.kt:931`에서 `LIVE` 정상·fallback·invalid ID 분기 확인. + - `HomeMainFragmentLoginGuardSourceTest.kt:128`에서 기존 공통 Live/Creator Action 사용 계약 확인. + +### 실행 환경 + +```text +OS: macOS 26.0 (25A354), x86_64 +Java: OpenJDK 17.0.15 +Gradle: 8.14.3 +Kotlin: 2.0.21 +기준 commit: a1aa9c224326e4f730f715a4eba20add33329aed +환경 변수: 별도 민감정보 확인 또는 출력 없음 +``` + +### 실행한 검증 + +| 명령 또는 수동 검증 | 결과 | 핵심 증거 | +|---|---|---| +| Phase 16 focused test와 live/creator action 회귀 `--rerun-tasks` | 성공 | `BUILD SUCCESSFUL in 7m 19s`, 39 tasks executed | +| Phase 16 focused test와 live/creator action 회귀 최종 재실행 | 성공 | 116 tests, failures 0, errors 0, `BUILD SUCCESSFUL in 24s` | +| `./gradlew :app:compileDebugKotlin :app:ktlintCheck` | 성공 | `BUILD SUCCESSFUL in 5s` | +| `./gradlew tasks --all --console=plain` | 성공 | 문서 변경 후 Gradle task 유효성 확인, `BUILD SUCCESSFUL in 3s` | +| `git diff --check` | 성공 | 출력 없음 | +| `git diff --name-only`와 Phase 16 Files 대조 | 성공 | 이 리뷰 보고서를 제외하면 production 4개, test 2개, 승인된 PRD·plan·test guide만 변경됨 | +| `./gradlew :app:testDebugUnitTest` | 실패 | 1186 tests 중 Phase 16 변경 경로 밖 `v2.creator.channel` 2건 실패 | +| 전체 suite 실패 3개 test method 개별 격리 실행 | 성공 | 각 `BUILD SUCCESSFUL`; 전체 실행의 실패가 개별 실행에서는 재현되지 않음 | +| 실제 추천 `LIVE` item 기기 터치 | 불가 | 서버 추천 데이터와 사용자 상태가 필요한 경로이므로 route 단위 테스트와 공통 액션 source 계약으로 대체 | + +전체 suite에서 처음 실패한 테스트는 아래 2건이다. + +- `CreatorChannelHomeViewModelTest > 채널 후원 성공 시 보유 can보다 큰 금액 차감은 0으로 보정한다` +- `CreatorChannelCommunityDetailViewModelTest > 채널 작성자는 유료 미구매 게시물도 이미지와 오디오 URL을 볼 수 있다` + +두 class만 재실행했을 때는 위 2건이 통과하고 같은 class의 다른 후원 테스트 1건이 실패했으며, 관련 3개 method를 각각 격리 실행하면 모두 통과했다. Phase 16 working tree에는 `v2.creator.channel` production/test 변경이 없다. 따라서 이 결과는 Phase 16 회귀 근거로 사용하지 않고, 범위 밖의 비결정적 전체 suite 관찰로 남긴다. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +Phase 16 요구사항 위반, 공통 접근 정책 우회, 다른 activity type route 회귀, 변경 범위 이탈은 확인되지 않았다. + +## 6. 발견 사항 상세 + +확정 발견 사항 없음. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. Phase 16에 추가할 신규 회귀 수정 Task와 create_goal objective가 없다. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | Phase 16 문서, production 4개, test 2개, 공통 action 호출 경로 대조 완료 | +| 후보 항목 판정 완료 | 충족 | Phase 16 확정 발견 사항 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 전환할 확정 review 항목 없음 | +| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | Phase 16 보류 항목 없음 | +| 검증 명령과 결과 기록 | 충족 | focused 116 tests, compile, ktlint, diff 검증과 전체 suite 관찰 기록 | + +**최종 결론:** 확정 발견 사항 없음. + +**남은 항목:** Phase 16 수정 항목은 없다. 전체 unit suite는 범위 밖의 비결정적 `v2.creator.channel` 실패로 완전한 green 상태가 아니며, 해당 영역을 별도 리뷰할 때 테스트 격리성과 `SharedPreferenceManager` 전역 상태 사용을 재현 조건으로 점검할 수 있다. + +## 9. 수정 후 검증 기록 + +수정 항목이 없어 해당 없음. diff --git a/docs/agent-guides/build-test-style.md b/docs/agent-guides/build-test-style.md index 0fea4fdd..65627b5b 100644 --- a/docs/agent-guides/build-test-style.md +++ b/docs/agent-guides/build-test-style.md @@ -107,6 +107,13 @@ Creator/Community Action Phase 테스트 예시: ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.community.action.*" ``` +Home Recommendation Phase 16 테스트 예시: +```bash +./gradlew :app:testDebugUnitTest \ + --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLayoutTest" \ + --tests "kr.co.vividnext.sodalive.v2.main.home.HomeMainFragmentLoginGuardSourceTest" +``` + CreatorChannel 현재 라이브 공통 컴포넌트 테스트 예시: ```bash ./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.ui.CreatorChannelCurrentLiveViewModelTest"