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 d5690c32..8d27762b 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 @@ -20,6 +20,7 @@ import kr.co.vividnext.sodalive.v2.access.AccessRequirement import kr.co.vividnext.sodalive.v2.access.ensureV2Access import kr.co.vividnext.sodalive.v2.chat.action.ChatActionCommand import kr.co.vividnext.sodalive.v2.chat.action.handleChatAction +import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog import kr.co.vividnext.sodalive.v2.common.CreatorActivityType import kr.co.vividnext.sodalive.v2.community.action.CommunityActionCommand import kr.co.vividnext.sodalive.v2.community.action.handleCommunityAction @@ -232,10 +233,26 @@ class HomeMainFragment : BaseFragment( adapter = popularCommunityAdapter } onCheerFollowAllClick = { creatorIds -> - homeRecommendationViewModel.followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds) + ensureV2Access(AccessRequirement.Login) { + showCheerCreatorsFollowAllConfirmDialog(creatorIds) + } } } + private fun showCheerCreatorsFollowAllConfirmDialog(creatorIds: List) { + V2ModalDialog( + activity = requireActivity(), + layoutInflater = layoutInflater, + title = getString(R.string.home_recommendation_follow_all_confirm_title), + desc = getString(R.string.home_recommendation_follow_all_confirm_description), + confirmButtonTitle = getString(R.string.home_recommendation_follow_all_confirm_button), + confirmButtonClick = { + homeRecommendationViewModel.followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds) + }, + cancelButtonTitle = getString(R.string.cancel) + ).show(screenWidth) + } + private fun setUpCreatorRankingAdapter() { binding.rvHomeCreatorRankings.apply { layoutManager = CreatorRankingAdapter.createGridLayoutManager(requireContext()) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePopularCommunityAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePopularCommunityAdapter.kt index acf3b458..836968ac 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePopularCommunityAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePopularCommunityAdapter.kt @@ -5,7 +5,6 @@ import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import coil.dispose import kr.co.vividnext.sodalive.R -import kr.co.vividnext.sodalive.common.image.BlurTransformation import kr.co.vividnext.sodalive.extensions.loadUrl import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationPopularCommunityPostSection import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationPopularCommunityPostUiModel @@ -51,9 +50,8 @@ class HomePopularCommunityAdapter( view.setOnFeedClick { feedItem -> onClickItem(feedItem as FeedItem.Community) } bindImage(item.item.creatorImageUrl, view.profileImageView()) bindImage( - imageUrl = item.item.imageUrl, + imageUrl = item.item.imageUrl.takeIf { !item.item.isLockedPaidPost() }, imageView = view.communityImageView(), - shouldBlur = item.item.price > 0 && !item.item.existOrdered, onImageLoaded = { width, height -> view.applyCommunityImageSize(width, height) } ) } @@ -61,7 +59,6 @@ class HomePopularCommunityAdapter( private fun bindImage( imageUrl: String?, imageView: android.widget.ImageView, - shouldBlur: Boolean = false, onImageLoaded: ((Int, Int) -> Unit)? = null ) { if (imageUrl.isNullOrBlank()) { @@ -74,11 +71,12 @@ class HomePopularCommunityAdapter( onImageLoaded?.invoke(result.drawable.intrinsicWidth, result.drawable.intrinsicHeight) } ) - if (shouldBlur) { - transformations(BlurTransformation(imageView.context, 25f, 2.5f)) - } } } } + + private fun FeedItem.Community.isLockedPaidPost(): Boolean { + return price > 0 && !existOrdered + } } } diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 76bd9e9c..69157e87 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -323,6 +323,9 @@ Community Follow all Following all + Follow all + Would you like to follow all creators in this list? + Follow More Collapse An unknown error occurred. try again. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index e11aa0b9..052edc0d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -323,6 +323,9 @@ コミュニティ すべてフォロー すべてフォロー中 + すべてフォロー + このリストのクリエイターをすべてフォローしますか? + フォローする もっと見る 折りたたむ 不明なエラーが発生しました。恐れ入りますが、もう一度お試しください。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2078176c..80d0effb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -322,6 +322,9 @@ 커뮤니티 모두 팔로우 하기 모두 팔로우 완료 + 모두 팔로우 + 목록의 크리에이터를 모두 팔로우하시겠어요? + 팔로우 하기 더보기 접기 알 수 없는 오류가 발생했습니다. 다시 시도해 주세요. 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 eaebc777..26cf571c 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 @@ -1022,18 +1022,6 @@ class HomeMainFragmentLayoutTest { assertEquals(null, aiCharacter(creatorId = -1L).toHomeRecommendationAiCharacterRoute()) } - @Test - fun `home popular community adapter applies blur when locked paid post image is loaded`() { - val source = projectFile( - "app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomePopularCommunityAdapter.kt" - ).readText() - - assertTrue(source.contains("imageUrl = item.item.imageUrl")) - assertTrue(source.contains("shouldBlur = item.item.price > 0 && !item.item.existOrdered")) - assertTrue(source.contains("BlurTransformation(imageView.context, 25f, 2.5f)")) - assertFalse(source.contains("item.item.imageUrl.takeIf { item.item.price <= 0 || item.item.existOrdered }")) - } - @Test fun `home live section height follows its items`() { val root = inflateView(R.layout.fragment_v2_main_home) @@ -1298,6 +1286,73 @@ class HomeMainFragmentLayoutTest { assertFalse(source.contains("followCreators(genreSectionKey")) } + @Test + fun `응원 크리에이터 모두 팔로우는 API 요청 전에 확인창을 연다`() { + val source = homeMainFragmentSource() + + assertTrue(source.contains("import kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog")) + assertTrue(source.contains("private fun showCheerCreatorsFollowAllConfirmDialog(creatorIds: List)")) + assertTrue( + source.contains( + "onCheerFollowAllClick = { creatorIds ->\n" + + " ensureV2Access(AccessRequirement.Login) {\n" + + " showCheerCreatorsFollowAllConfirmDialog(creatorIds)\n" + + " }\n" + + " }" + ) + ) + assertTrue(source.contains("V2ModalDialog(")) + assertTrue(source.contains("title = getString(R.string.home_recommendation_follow_all_confirm_title)")) + assertTrue(source.contains("desc = getString(R.string.home_recommendation_follow_all_confirm_description)")) + assertTrue( + source.contains( + "confirmButtonTitle = getString(R.string.home_recommendation_follow_all_confirm_button)" + ) + ) + assertTrue(source.contains("cancelButtonTitle = getString(R.string.cancel)")) + assertTrue( + source.contains( + "confirmButtonClick = {\n" + + " homeRecommendationViewModel.followCreators(SECTION_KEY_CHEER_CREATORS, creatorIds)\n" + + " }" + ) + ) + assertTrue(source.contains(".show(screenWidth)")) + } + + @Test + fun `모두 팔로우 확인창 문자열은 다국어로 제공한다`() { + val context = ApplicationProvider.getApplicationContext() + val koreanContext = context.createConfigurationContext( + Configuration(context.resources.configuration).apply { setLocale(Locale.KOREAN) } + ) + val englishContext = context.createConfigurationContext( + Configuration(context.resources.configuration).apply { setLocale(Locale.ENGLISH) } + ) + val japaneseContext = context.createConfigurationContext( + Configuration(context.resources.configuration).apply { setLocale(Locale.JAPANESE) } + ) + + assertEquals("모두 팔로우", koreanContext.getString(R.string.home_recommendation_follow_all_confirm_title)) + assertEquals( + "목록의 크리에이터를 모두 팔로우하시겠어요?", + koreanContext.getString(R.string.home_recommendation_follow_all_confirm_description) + ) + assertEquals("팔로우 하기", koreanContext.getString(R.string.home_recommendation_follow_all_confirm_button)) + assertEquals("Follow all", englishContext.getString(R.string.home_recommendation_follow_all_confirm_title)) + assertEquals( + "Would you like to follow all creators in this list?", + englishContext.getString(R.string.home_recommendation_follow_all_confirm_description) + ) + assertEquals("Follow", englishContext.getString(R.string.home_recommendation_follow_all_confirm_button)) + assertEquals("すべてフォロー", japaneseContext.getString(R.string.home_recommendation_follow_all_confirm_title)) + assertEquals( + "このリストのクリエイターをすべてフォローしますか?", + japaneseContext.getString(R.string.home_recommendation_follow_all_confirm_description) + ) + assertEquals("フォローする", japaneseContext.getString(R.string.home_recommendation_follow_all_confirm_button)) + } + @Test fun `home recommendation viewmodel does not keep android context`() { val viewModelSource = homeRecommendationViewModelSource() 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 c4a61c90..b5122347 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 @@ -195,6 +195,21 @@ class HomeMainFragmentLoginGuardSourceTest { assertTrue(clickSource.contains("CreatorActionCommand.Profile(route.creatorId)")) } + @Test + fun `HomeMainFragment 응원 크리에이터 모두 팔로우는 확인창보다 로그인 가드를 먼저 실행한다`() { + val source = projectFile( + "app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt" + ).readText() + + val setupSource = source.substringFrom("private fun setUpRecommendationAdapters()") + assertTrue(setupSource.contains("onCheerFollowAllClick = { creatorIds ->")) + assertBefore( + setupSource, + "ensureV2Access(AccessRequirement.Login)", + "showCheerCreatorsFollowAllConfirmDialog(creatorIds)" + ) + } + @Test fun `HomeMainFragment loading dialog는 통합 loading state로 제어한다`() { val source = projectFile( diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomePopularCommunityAdapterTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomePopularCommunityAdapterTest.kt new file mode 100644 index 00000000..048e0e30 --- /dev/null +++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomePopularCommunityAdapterTest.kt @@ -0,0 +1,153 @@ +package kr.co.vividnext.sodalive.v2.main.home + +import android.app.Application +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import coil.load +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify +import kr.co.vividnext.sodalive.R +import kr.co.vividnext.sodalive.common.ImageLoaderProvider +import kr.co.vividnext.sodalive.extensions.loadUrl +import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationPaidStatus +import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationPopularCommunityPostSection +import kr.co.vividnext.sodalive.v2.main.home.model.HomeRecommendationPopularCommunityPostUiModel +import kr.co.vividnext.sodalive.v2.main.home.ui.HomePopularCommunityAdapter +import kr.co.vividnext.sodalive.v2.widget.feed.FeedItem +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [28], application = Application::class) +class HomePopularCommunityAdapterTest { + + @Before + fun setUp() { + if (!ImageLoaderProvider.isInitialized) { + ImageLoaderProvider.init(RuntimeEnvironment.getApplication()) + } + mockkStatic("kr.co.vividnext.sodalive.extensions.ImageExtensionsKt") + every { any().loadUrl(any(), any()) } just Runs + } + + @After + fun tearDown() { + unmockkStatic("kr.co.vividnext.sodalive.extensions.ImageExtensionsKt") + } + + @Test + fun `유료 미구매 bind는 원본을 로드하지 않고 이전 요청과 drawable을 제거한다`() { + val adapter = adapterWith( + communityPost( + imageUrl = "https://example.com/locked.png", + price = 30, + existOrdered = false + ) + ) + val holder = createViewHolder(adapter) + val imageView = holder.itemView.findViewById(R.id.iv_feed_community_image) + val disposable = imageView.load(ColorDrawable(Color.BLUE), ImageLoaderProvider.imageLoader) + assertFalse(disposable.isDisposed) + + adapter.onBindViewHolder(holder, 0) + + verify(exactly = 0) { imageView.loadUrl(any(), any()) } + assertTrue(disposable.isDisposed) + assertNull(imageView.drawable) + assertEquals( + View.VISIBLE, + holder.itemView.findViewById(R.id.ll_feed_community_paid_overlay).visibility + ) + assertEquals( + "30", + holder.itemView.findViewById(R.id.tv_feed_community_price).text.toString() + ) + } + + @Test + fun `무료와 구매 완료 bind는 원본 이미지 URL을 로더에 전달한다`() { + val freeImageUrl = "https://example.com/free.png" + val purchasedImageUrl = "https://example.com/purchased.png" + val adapter = adapterWith( + communityPost(imageUrl = freeImageUrl, price = 0, existOrdered = false), + communityPost(imageUrl = purchasedImageUrl, price = 30, existOrdered = true) + ) + val holder = createViewHolder(adapter) + val imageView = holder.itemView.findViewById(R.id.iv_feed_community_image) + + adapter.onBindViewHolder(holder, 0) + adapter.onBindViewHolder(holder, 1) + + verify(exactly = 1) { imageView.loadUrl(freeImageUrl, any()) } + verify(exactly = 1) { imageView.loadUrl(purchasedImageUrl, any()) } + assertEquals(View.GONE, holder.itemView.findViewById(R.id.ll_feed_community_paid_overlay).visibility) + } + + private fun adapterWith( + vararg items: HomeRecommendationPopularCommunityPostUiModel + ): HomePopularCommunityAdapter { + return HomePopularCommunityAdapter(onClickItem = {}).apply { + submitSection(HomeRecommendationPopularCommunityPostSection(items.toList())) + } + } + + private fun createViewHolder( + adapter: HomePopularCommunityAdapter + ): HomePopularCommunityAdapter.CommunityViewHolder { + val context = RuntimeEnvironment.getApplication() + val parent = RecyclerView(context).apply { + layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false) + } + return adapter.onCreateViewHolder(parent, 0) + } + + private fun communityPost( + imageUrl: String, + price: Int, + existOrdered: Boolean + ): HomeRecommendationPopularCommunityPostUiModel { + val paidStatus = when { + price == 0 -> HomeRecommendationPaidStatus.Free + existOrdered -> HomeRecommendationPaidStatus.Purchased + else -> HomeRecommendationPaidStatus.Paid(price) + } + return HomeRecommendationPopularCommunityPostUiModel( + item = FeedItem.Community( + feedId = "feed-1", + creatorId = "creator-1", + creatorName = "크리에이터", + creatorImageUrl = "", + postId = "post-1", + bodyText = "본문", + keywordText = "", + createdAtText = "방금 전", + commentCount = 1, + likeCount = 2, + imageUrl = imageUrl, + audioUrl = null, + price = price, + existOrdered = existOrdered, + showKeyword = false + ), + paidStatus = paidStatus + ) + } +} diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md b/docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md index 2a830f4b..6bdac4df 100644 --- a/docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md @@ -41,6 +41,28 @@ - 신규 필요 후보: 최근 활동 카드, 최근 데뷔 크리에이터 카드, 장르별/응원 크리에이터 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 추가 @@ -409,6 +431,79 @@ - 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: 사업자 정보 접기/더보기 구현 @@ -582,7 +677,7 @@ - 실행: `./gradlew :app:ktlintCheck` - 기대 결과: 모두 성공. -- [ ] **Task 12.2: 수동 확인** `[blocked: 연결된 Android 기기 없음]` +- [x] **Task 12.2: 수동 확인** - 확인 항목: - 추천 tab 선택 상태 - tab 전환은 글자 터치로만 동작하고 swipe 전환은 없음 @@ -590,7 +685,7 @@ - 섹션 순서가 PRD 도식과 일치 - 제외 섹션 미노출 - 빈 리스트 섹션 숨김 - - 첫 오디오 태그 조건 + - Phase 12B에서 제거된 첫 오디오 섹션과 관련 태그 미노출 - activity type 다국어 문자열 - 방금 활동한 크리에이터 목적지별 클릭 이동 - AI 캐릭터 item 크리에이터 채널 클릭 이동 @@ -606,6 +701,48 @@ - 구현 내용: 실행 명령, 결과, 실패 시 원인과 후속 조치를 문서 하단 `검증 기록`에 누적한다. - 주의: 기존 검증 기록은 삭제하거나 덮어쓰지 않는다. +#### 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`는 출력 없이 성공했다. + --- ## 확인 완료 사항 @@ -721,6 +858,16 @@ ### 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` @@ -737,6 +884,49 @@ - 수정: `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 라우팅 변경** @@ -891,7 +1081,180 @@ git diff --check - 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를 추가했다. + +## 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` | + ## Verification Log +- 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` 결과 연결된 기기/에뮬레이터가 없어 실기기 수동 확인은 수행하지 못했다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/prd.md b/docs/20260601_메인_홈_추천_UI와_API_연동/prd.md index 82c47990..a7d3cc86 100644 --- a/docs/20260601_메인_홈_추천_UI와_API_연동/prd.md +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/prd.md @@ -6,9 +6,10 @@ Figma `home_001` 화면(`24:5514`)을 기준으로 메인 홈 추천 영역을 --- ## 2. Problem -- 메인 홈 추천 화면은 라이브, 배너, 최근 활동, 신규 데뷔, 첫 오디오 콘텐츠, AI 캐릭터, 응원 크리에이터, 인기 커뮤니티 등 여러 데이터 섹션을 한 화면에 표시해야 한다. `genreCreators` 응답 계약은 유지하지만 장르별 크리에이터 UI는 추천 탭에 표시하지 않는다. +- 메인 홈 추천 화면은 라이브, 배너, 최근 활동, 신규 데뷔, AI 캐릭터, 응원 크리에이터, 인기 커뮤니티 등 여러 데이터 섹션을 한 화면에 표시해야 한다. `genreCreators` 응답 계약은 유지하지만 장르별 크리에이터 UI는 추천 탭에 표시하지 않는다. - Figma에는 이미 `live`, `banner`, `section-title`, `contents`, `chat-thumbnail`, `profile`, `feed` 등 재사용 가능한 형태가 다수 포함되어 있으므로, 기존에 생성된 widget을 최대한 활용해야 한다. - `HomeActiveCreatorItem.activityType`은 백엔드 enum code 그대로 표시하면 사용자에게 부자연스러우므로 `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY`를 다국어 처리 가능한 표시 문자열로 변환해야 한다. +- `모두 팔로우 하기` 버튼은 현재 터치 즉시 여러 크리에이터를 팔로우하는 API를 호출하므로, 사용자가 대상과 영향을 다시 확인하지 못한 채 의도하지 않은 크리에이터까지 팔로우할 수 있다. - 사업자 정보 섹션은 별도 외부 라이브러리 없이 최대 3줄 말줄임과 더보기/접기 토글을 제공해야 한다. - Figma에 포함된 일부 섹션은 이번 범위에서 제외되어야 하므로 구현 범위를 명확히 분리해야 한다. @@ -23,6 +24,7 @@ Figma `home_001` 화면(`24:5514`)을 기준으로 메인 홈 추천 영역을 - `추천 필모그래피`, `또 다른 모습` 섹션은 만들지 않는다. - 최하단 사업자 정보 섹션은 표시하되, 기본 최대 3줄 말줄임과 `더보기`/`접기` 토글을 제공한다. - `HomeActiveCreatorItem.activityType`은 백엔드 code를 앱 내부 enum 또는 mapper로 변환하고, 표시 문구는 string resource 기반 다국어 처리를 적용한다. +- 여러 크리에이터를 한 번에 팔로우하는 모든 `모두 팔로우` 동작은 API 호출 전에 공통 확인창을 표시하고, 사용자가 명시적으로 확인한 경우에만 기존 팔로우 API를 호출한다. - API DTO, 화면 상태, empty/error/loading 정책, click routing은 구현 계획에서 검증 가능하도록 정리한다. --- @@ -38,6 +40,9 @@ Figma `home_001` 화면(`24:5514`)을 기준으로 메인 홈 추천 영역을 - 이번 범위에서 `랭킹`, `팔로잉`용 별도 Fragment 또는 숨김 View를 미리 만들지 않는다. - 기존 레거시 홈 화면 전체 리팩터링은 포함하지 않는다. - Figma에 없는 skeleton loading, shimmer, 임의 애니메이션, 추가 badge는 만들지 않는다. +- 모두 팔로우 확인 절차를 위해 신규 Dialog component나 layout을 만들지 않고 기존 `V2ModalDialog`를 재사용한다. +- 모두 팔로우 API endpoint, request body, 성공·실패 처리와 완료 버튼 UI는 변경하지 않는다. +- 현재 숨김 상태인 `genreCreators` 섹션이나 모두 팔로우 버튼을 다시 노출하지 않는다. --- @@ -51,6 +56,7 @@ Figma `home_001` 화면(`24:5514`)을 기준으로 메인 홈 추천 영역을 - 사용자는 홈에서 현재 라이브 중인 크리에이터와 추천 콘텐츠를 빠르게 탐색하고 싶다. - 사용자는 배너를 스와이프하거나 터치해 관련 이벤트, 크리에이터, 시리즈, 외부 링크 또는 앱 내부 딥링크로 이동하고 싶다. - 사용자는 최근 활동한 크리에이터의 활동 유형을 `라이브`, `오디오`, `커뮤니티`처럼 이해 가능한 문구로 보고 싶다. +- 사용자는 여러 크리에이터를 한 번에 팔로우하기 전에 확인창에서 동작을 검토하고, 확인하거나 취소할 수 있기를 원한다. - 사용자는 인기 커뮤니티 글을 읽다가 사업자 정보가 길어도 홈 화면 탐색이 과도하게 방해받지 않기를 기대한다. - 개발자는 기존 v2 widget을 최대한 재사용해 화면별 UI 중복과 스타일 차이를 줄이고 싶다. @@ -196,7 +202,7 @@ Figma 최하단 사업자 정보 섹션(`218:2058`)은 외부 라이브러리 - 세로 스크롤 시 title-bar와 `TextTabBarView`는 화면에 유지하고, `TextTabBarView` 아래 추천 content 영역만 스크롤되도록 구성한다. ### 모두 팔로우 하기 -`최근 응원이 많은 크리에이터` 섹션에는 모두 팔로우 버튼을 제공한다. `장르별 크리에이터`는 추천 탭 UI에 표시하지 않으므로 모두 팔로우 버튼도 제공하지 않는다. +여러 크리에이터를 한 번에 팔로우하는 모든 `모두 팔로우` 동작은 API 호출 전에 공통 확인 절차를 거친다. 현재 구현 대상은 실제 노출 중인 `최근 응원이 많은 크리에이터` 섹션으로 제한한다. `장르별 크리에이터`는 추천 탭 UI에 표시하지 않으므로 모두 팔로우 버튼과 확인창도 제공하지 않는다. #### API Requirements - API는 `POST /api/v2/home/recommendations/creators/follow`를 사용한다. @@ -228,6 +234,23 @@ data class FollowRecommendedCreatorsRequest( - 문구는 string resource로 관리한다. - 버튼 상태는 API success 후 중복 호출되지 않도록 disabled 또는 click 무시 상태로 전환한다. +#### Confirm Dialog Requirements +- 기본 상태의 `모두 팔로우 하기` 버튼을 터치하면 기존 `V2ModalDialog`의 취소·확인 2버튼 구성을 표시한다. +- 최초 버튼 터치만으로는 `POST /api/v2/home/recommendations/creators/follow`를 호출하지 않는다. +- Dialog 제목은 `모두 팔로우`, 설명은 `목록의 크리에이터를 모두 팔로우하시겠어요?`, 취소 버튼은 `취소`, 확인 버튼은 `팔로우 하기`로 표시한다. +- `팔로우 하기`를 터치하면 Dialog를 닫은 뒤 해당 표시 섹션의 `creatorIds`로 기존 모두 팔로우 API를 한 번 호출한다. +- `취소`를 터치하면 Dialog만 닫고 API를 호출하거나 버튼 상태를 변경하지 않는다. +- 완료 상태 버튼 또는 빈 `creatorIds`에서는 Dialog와 API를 모두 실행하지 않는다. +- Dialog 바깥 영역과 시스템 뒤로가기에 대한 닫기 정책은 기존 `V2ModalDialog`의 non-cancelable 동작을 유지하며, 취소는 명시적인 `취소` 버튼으로 수행한다. +- 공통 정책은 이후 다른 화면이나 섹션에 `모두 팔로우` 동작이 추가될 때도 동일하게 적용하지만, 이번 구현에서는 범용 wrapper나 신규 추상화를 미리 만들지 않는다. +- 표시 문자열은 아래 resource key로 한국어·영어·일본어에 모두 제공하고, 기존 `cancel` resource를 재사용한다. + +| Resource key | 한국어 | English | 日本語 | +|---|---|---|---| +| `home_recommendation_follow_all_confirm_title` | `모두 팔로우` | `Follow all` | `すべてフォロー` | +| `home_recommendation_follow_all_confirm_description` | `목록의 크리에이터를 모두 팔로우하시겠어요?` | `Would you like to follow all creators in this list?` | `このリストのクリエイターをすべてフォローしますか?` | +| `home_recommendation_follow_all_confirm_button` | `팔로우 하기` | `Follow` | `フォローする` | + ### 추천 페이지 위젯 수정 추천 페이지에 표시되는 기존 widget은 화면 요구사항에 맞춰 최소 범위로 확장한다. 별도 PRD로 분리하지 않고, 메인 홈 추천 UI의 표시 계약으로 이 문서에 함께 기록한다. @@ -277,10 +300,6 @@ HomeRecommendation 화면 │ ├─ Figma instance: section-title, creater │ ├─ 재사용: view_section_title │ └─ 신규: 최근 데뷔 크리에이터 카드 UI, item root `185dp x 234dp`, radius `14dp` 전체 clip - ├─ 첫 오디오 콘텐츠: firstAudioContents - │ ├─ Figma instance: contents, profile - │ ├─ 재사용: v2.widget.AudioContentCardView - │ └─ 신규: 카드 하단 profile 결합 adapter item 필요 여부 확인 ├─ AI 캐릭터: aiCharacters │ ├─ Figma instance: section-title, chat-thumbnail │ ├─ 재사용: view_section_title @@ -334,6 +353,7 @@ HomeRecommendation 화면 - `link`가 웹 URL이면 `Intent.ACTION_VIEW`를 사용해 외부 웹 URL로 이동한다. - `link`가 내부 딥링크이면 기존 딥링크 실행 정책에 따라 앱 내부 딥링크를 실행한다. - 웹 URL도 내부 딥링크도 아닌 `link`는 이동하지 않는다. +- 모두 팔로우 확인창의 확인 버튼을 빠르게 반복 터치해도 기존 `V2ModalDialog`의 중복 클릭 방지 계약에 따라 API 요청은 한 번만 시작한다. --- @@ -346,8 +366,9 @@ HomeRecommendation 화면 - 각 섹션 제목은 기존 `view_section_title`을 사용한다. - `최근 데뷔한 크리에이터` item은 기존 `205dp x 259dp` 비율을 유지해 `185dp x 234dp`로 축소하고, 이미지뿐 아니라 dim gradient와 닉네임을 포함한 item root 전체가 `14dp` rounded corner로 잘려야 한다. - `최근 응원이 많은 크리에이터`의 모두 팔로우 버튼은 success 전/후 상태가 명확히 구분되어야 한다. `장르별 크리에이터`는 추천 탭 UI에 표시하지 않으므로 버튼도 노출하지 않는다. +- `최근 응원이 많은 크리에이터`의 기본 상태 모두 팔로우 버튼을 터치하면 기존 V2 공통 디자인의 확인창이 먼저 표시되고, 사용자의 확인 전에는 네트워크 요청이 시작되지 않아야 한다. - 인기 커뮤니티는 keyword 없이 본문, 선택적 이미지, 유료 잠금 상태, reaction row를 보여야 한다. -- 유료 미구매 커뮤니티 이미지는 내용을 바로 읽을 수 없도록 blur/lock overlay가 적용되어야 한다. +- 유료 미구매 커뮤니티는 원본 `imageUrl`을 로드하지 않고 lock overlay와 가격 capsule만 표시한다. 별도 안전 preview 필드가 제공되기 전까지 client-side blur를 위해 원본 이미지를 요청하지 않는다. - 유료 구매 완료 또는 무료 커뮤니티 이미지는 overlay 없이 표시하고, `구매완료` 태그는 표시하지 않는다. - 가로 목록은 화면 밖 다음 item이 일부 보이도록 Figma의 peek 느낌을 유지한다. - 커뮤니티 본문과 사업자 정보는 긴 텍스트가 화면 폭을 밀어내지 않아야 한다. @@ -359,11 +380,12 @@ HomeRecommendation 화면 ## 9. Technical Constraints - 현재 프로젝트는 Android XML Views + Kotlin + ViewBinding 기반이므로 XML layout, RecyclerView/adapter, custom view 패턴을 우선한다. -- UI는 기존 `kr.co.vividnext.sodalive.v2.main.HomeMainFragment`에 구현한다. +- UI는 현재 canonical 경로인 `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`의 `kr.co.vividnext.sodalive.v2.main.home.HomeMainFragment`에 구현한다. 과거 `kr.co.vividnext.sodalive.v2.main.HomeMainFragment` 경로는 현재 존재하지 않는다. - `HomeMainFragment`는 이번 범위에서 추천 content만 직접 구성하며, `ViewPager2`, `FragmentStateAdapter`, tab별 신규 Fragment 3개 구조는 도입하지 않는다. - `TextTabBarView` 아래 추천 content 영역만 `NestedScrollView` 또는 동등한 단일 세로 스크롤 컨테이너로 구성한다. - 신규 API/Repository/ViewModel 및 그와 연결된 하위 코드는 `kr.co.vividnext.sodalive.v2` 패키지 하위에 작성한다. - 기존 `kr.co.vividnext.sodalive.v2.widget.*` 컴포넌트를 우선 재사용한다. +- 모두 팔로우 확인 UI는 기존 `kr.co.vividnext.sodalive.v2.components.modal.V2ModalDialog`를 재사용하고 공통 모달 component나 layout을 수정하지 않는다. - 기존 API 인터페이스에 메서드를 추가하지 않고, 홈 추천 전용 신규 API 인터페이스를 만든다. - 이미지 로딩 라이브러리를 새로 추가하지 않고 기존 호출부의 이미지 로딩 방식을 따른다. - 외부 라이브러리를 추가하지 않는다. @@ -380,13 +402,15 @@ HomeRecommendation 화면 - `LIVE`, `LIVE_REPLAY`, `AUDIO`, `COMMUNITY` 표시 문자열이 string resource 기반으로 매핑된다. - `HomeMainFragment`에서 `view_title_bar_home`, `TextTabBarView`, `view_section_title` 재사용이 확인된다. - `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` 상태로 변경된다. +- 최근 응원이 많은 크리에이터의 기본 상태 모두 팔로우 버튼 터치 직후에는 확인창만 표시되고 API 호출 횟수는 `0`이다. +- 확인창에서 `취소`를 선택하면 API 호출 횟수는 계속 `0`이고 버튼 상태가 유지되며, `팔로우 하기`를 선택하면 기존 API가 정확히 한 번 호출된다. +- 모두 팔로우 확인창의 제목·설명·확인 버튼 문구가 한국어·영어·일본어 string resource로 제공된다. - 사업자 정보는 기본 3줄 말줄임, 더보기 후 전체 표시, 접기 후 3줄 복귀가 동작한다. - 기존 v2 widget 재사용 범위가 구현 diff에서 확인된다. - 관련 unit test, resource merge, `./gradlew :app:testDebugUnitTest` 또는 구현 범위에 맞는 단일 테스트가 성공한다. @@ -399,11 +423,15 @@ HomeRecommendation 화면 - 인증 token 처리 방식은 아직 제공되지 않았다. 구현 전 기존 `HomeApi` 확장 지점과 공통 인증 interceptor 사용 여부를 확인해야 한다. - `cheerCreators`의 profile grid는 기존 profile widget이 저장소에 있는지 추가 확인 후 재사용/신규를 결정한다. `genreCreators`는 응답/매핑 계약만 유지하고 추천 탭 UI에는 표시하지 않는다. - 배너 이동 정책은 `type` 없이 `eventItem`, `creatorId`, `seriesId`, `link` 값 기준으로 처리하는 것으로 확정됐다. `link`가 내부 딥링크이면 기존 딥링크 실행 정책을 사용한다. -- `activityAt`, `releaseDate`, `createdAt`, `beginDateTime`의 표시 포맷은 기존 시간 formatter 재사용 여부를 구현 계획에서 확인한다. +- `activityAt`, `createdAt`, `beginDateTime`의 표시 포맷은 기존 시간 formatter 재사용 여부를 구현 계획에서 확인한다. --- ## 12. 검증 기록 +- 2026-07-31: PRD와 계획/TASK 보완 후 신규 범위의 placeholder·모순·요구사항 추적성을 자체 검토했다. `git status --short`로 변경 파일이 두 문서뿐임을 확인했고, placeholder 검색과 `git diff --check`는 출력 없이 성공했다. `./gradlew tasks --all`은 최초 sandbox 실행에서 사용자 Gradle cache lock 접근 제한으로 실패했으나 승인된 동일 명령 재실행은 `BUILD SUCCESSFUL`로 완료되어 계획에 기재한 Gradle task 이름의 유효성을 확인했다. production·test·resource 파일은 변경하지 않았다. +- 2026-07-31: `deep-interview` 결과, 모든 `모두 팔로우` 동작에 API 호출 전 확인 절차를 적용하는 공통 정책을 확정했다. 현재 구현 대상은 실제 노출 중인 `최근 응원이 많은 크리에이터` 섹션으로 제한하고, 숨겨진 `genreCreators` UI는 다시 노출하지 않는다. 저장소의 `HomeMainFragment`가 `cheerCreators` 전체 id를 `HomeRecommendationViewModel.followCreators()`에 즉시 전달하는 현재 흐름과, 취소·확인 2버튼 및 중복 클릭 방지를 지원하는 기존 `V2ModalDialog`를 확인했다. 확인창은 제목 `모두 팔로우`, 설명 `목록의 크리에이터를 모두 팔로우하시겠어요?`, 확인 `팔로우 하기`, 취소 `취소`로 확정하고 한국어·영어·일본어 resource를 계획했다. 이번 요청은 PRD와 계획/TASK 보완까지만 수행하며 구현 코드는 변경하지 않는다. +- 2026-07-31: `REV-P7-001` 후속으로 유료 미구매 인기 커뮤니티의 원본 이미지 미로드 보안 결정을 PRD에 재정렬했다. commit `2d58a876`에서 client-side blur 원본 로드가 재도입된 회귀를 복구해, 무료·구매 완료 이미지는 기존대로 로드하고 유료 미구매는 원본 `imageUrl` 요청 없이 lock overlay와 가격만 유지한다. +- 2026-07-31: R7.1 RED/GREEN 검증으로 `HomeMainFragmentLayoutTest.home popular community adapter does not load original image for locked paid post`를 먼저 추가해 focused test가 `HomeMainFragmentLayoutTest.kt:1032` assertion 실패로 RED가 됨을 확인했다. `HomePopularCommunityAdapter` 최소 수정 후 동일 focused test는 BUILD SUCCESSFUL로 GREEN 전환됐고, 후속으로 `HomeMainFragmentLayoutTest` 전체, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check`가 모두 성공했다. - 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을 확인했다. @@ -467,3 +495,15 @@ HomeRecommendation 화면 - 서버 버그 등으로 `creatorId <= 0L`이면 route를 만들지 않고 이동하지 않는다. - 공통 라이브 진입 흐름을 재사용해 비로그인, 본인인증 필요, 성인 콘텐츠 보기 설정 필요 상태를 기존 접근 정책으로 처리한다. - 다른 activity type의 기존 라우팅과 공통 접근 정책 자체는 변경하지 않는다. + +### 2026-07-31 변경: 모두 팔로우 확인 절차 +- 여러 크리에이터를 한 번에 팔로우하는 모든 `모두 팔로우` 동작은 API 호출 전에 기존 `V2ModalDialog` 확인창을 표시한다. +- 공통 정책의 현재 구현 대상은 실제 노출 중인 `최근 응원이 많은 크리에이터` 섹션이며, 숨겨진 `genreCreators` 섹션은 구현 범위에 포함하지 않는다. +- 최초 버튼 터치와 취소에서는 API를 호출하지 않고, 확인창의 `팔로우 하기`를 터치한 경우에만 기존 `creatorIds`와 API 계약으로 요청한다. +- Dialog 문구는 한국어·영어·일본어 string resource로 제공하며, API·로딩·성공·실패·완료 버튼 상태 계약은 변경하지 않는다. +- 이번 요청에서는 PRD와 구현 계획/TASK 문서만 보완하고 production·test·resource 구현은 진행하지 않는다. + +### 2026-07-31 변경: 인기 커뮤니티 유료 미구매 원본 이미지 미로드 복구 +- 유료 미구매 인기 커뮤니티 item은 원본 `imageUrl`을 image loader에 전달하지 않는다. +- 기존 Coil 요청과 drawable을 비우는 방식으로 원본 네트워크 요청/cache 노출을 막고, `FeedCommunityView.bind()`의 lock overlay와 가격 capsule 표시는 유지한다. +- 무료 또는 구매 완료 item의 이미지 로딩, 커뮤니티 상세 이동, 구매 권한 판정, API/DTO 계약은 변경하지 않는다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase1-existing-boundary-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase1-existing-boundary-review.md new file mode 100644 index 00000000..f2bd8d53 --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase1-existing-boundary-review.md @@ -0,0 +1,88 @@ +# Phase 1 기존 구조·구현 경계 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 1 / 기존 구조 확인과 구현 경계 고정 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 1 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: Phase 1에서 확정한 v2 화면·공용 위젯·DI 경계가 후속 구현에서도 유지됐는지 확인한다. +- 포함: `v2/main`, `v2/widget`, `AppDI.kt`, 관련 레이아웃과 현재 파일 배치. +- 제외: 후속 Phase의 상세 UI 동작과 실기기 시각 검증. + +## 3. 판정 기준 + +`sample-review.md`의 심각도와 상태 기준을 적용하고, 현재 경로가 후속 리팩터링으로 이동한 경우에는 기능·참조가 함께 보존됐는지로 판정했다. + +## 4. 검토한 근거 + +- 계획: Phase 1의 기존 `HomeMainFragment`, 재사용 widget, DI 경계 확인 항목. +- 코드: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`, `app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt`. +- 검증: v2 home focused test, resource merge, compile, ktlint 성공. 최신 전체 1,198 tests 중 실패 0·error 0. +- 환경: macOS, Android Gradle 프로젝트, 연결 기기 기반 수동 확인 제외. + +## 5. 발견 사항 요약 + +| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | +|---|---|---|---|---|---| +| `REV-P1-001` | Low | 수정 완료 | 활성 문서의 HomeMainFragment 경로가 현재 파일과 다르다 | `Task R1.1` | `P1-R1` | + +## 6. 발견 사항 상세 + +### REV-P1-001 — 활성 문서의 HomeMainFragment 경로가 현재 파일과 다르다 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** v2 홈 구현 경계·파일 추적성 +- **소유 Task:** `Task R1.1` / `P1-R1` + +**관찰 내용** + +현재 Fragment는 `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`에 있고 계획의 초기 `app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt`는 존재하지 않는다. 과거 Task 이력은 보존 대상이지만 PRD Technical Constraints도 과거 패키지를 현재 제약으로 안내하고 있어 활성 경로 안내는 정정이 필요하다. + +**근거·재현** + +1. `test -f app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeMainFragment.kt`는 성공한다. +2. `test -f app/src/main/java/kr/co/vividnext/sodalive/v2/main/HomeMainFragment.kt`는 실패한다. +3. `prd.md:387`과 `plan-task.md` Phase 1·5의 활성 안내/경로가 과거 위치를 사용한다. + +**영향** + +런타임 회귀는 없지만 후속 작업이 미존재 파일을 근거로 범위를 잡거나 리뷰 근거 경로를 잘못 인용할 수 있다. + +**권장 조치** + +기존 완료 이력은 삭제하지 않고 PRD와 Phase 1에 현재 canonical 경로를 명시한다. + +**판정 기록** + +- 2026-07-31 — 파일 존재 검사와 문서 검색으로 활성 경로 불일치를 확정했다. +- 2026-07-31 — `P1-R1` 문서 정정과 경로 검색, `git diff --check` 성공으로 수정 완료 판정했다. + +## 7. 확정 항목의 plan·goal 전환 + +`plan-task.md` Phase 1에 `Task R1.1` / `P1-R1`을 추가했다. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | 구조·의존성 경계 대조 완료 | +| 후보 항목 판정 완료 | 충족 | `REV-P1-001` 수정 완료 | +| 확정 항목 plan 반영 | 충족 | `Task R1.1` / `P1-R1` 추가 | +| 검증 명령과 결과 기록 | 충족 | `plan-task.md` Verification Log 연결 | + +**최종 결론:** 승인 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +- 2026-07-31 — `Task R1.1` 실행으로 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`를 명시하고 과거 경로 미존재를 덧붙였다. `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"`로 파일 상태를 확인했고, `rg` 경로 검색 및 `git diff --check`는 성공했다. 남은 항목은 없다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase10-banner-routing-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase10-banner-routing-review.md new file mode 100644 index 00000000..32f4f5fa --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase10-banner-routing-review.md @@ -0,0 +1,57 @@ +# Phase 10 배너 라우팅 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 10 / 배너 이동 정책 연결 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 10 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: event→creator→series→link 우선순위와 web/internal link 안전 처리를 확인한다. +- 포함: 배너 route model/intent 생성, Fragment callback, 관련 tests. +- 제외: 외부 브라우저·딥링크 목적 앱의 실제 실행 결과. + +## 3. 판정 기준 + +우선순위 보존, blank/malformed/허용하지 않은 scheme no-op, 기존 extra 계약과 로그인 정책 유지로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeRecommendationUiModels.kt` 배너 route, `HomeMainFragment` 배너 callback. +- 테스트: `HomeMainFragmentLayoutTest` 배너 route/intent 계약. +- 검증: home/modal focused test·compile·ktlint 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 여러 payload 우선순위와 링크 분류 guard가 현재 테스트로 고정되어 있다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | route·intent·callback 검토 완료 | +| 후보 항목 판정 완료 | 충족 | 확정 후보 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | focused 검증 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음. Phase 12의 배너 목적지별 클릭 이동은 사용자 확인으로 완료됐다. + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase11-creator-item-routing-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase11-creator-item-routing-review.md new file mode 100644 index 00000000..01bd3efd --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase11-creator-item-routing-review.md @@ -0,0 +1,57 @@ +# Phase 11 크리에이터 item 라우팅 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 11 / 최근 활동·AI 캐릭터 터치 액션 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 11 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: activity type별 route와 AI 캐릭터의 creator channel 이동이 유효 ID만 사용하도록 구성됐는지 확인한다. +- 포함: recent activity route, AI creatorId mapper/route, adapter callback과 tests. +- 제외: COMMUNITY와 LIVE의 후속 목적지 변경은 Phase 13·16에서 별도 판정. + +## 3. 판정 기준 + +표시 ID와 route ID 분리, invalid ID no-op, 목적지 extra, 후속 승인 변경 반영으로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeRecommendationUiModels.kt`, `HomeAiCharacterAdapter.kt`, `HomeRecentActivityCreatorAdapter.kt`, `HomeMainFragment.kt`. +- 테스트: `HomeMainFragmentLayoutTest`의 recent activity/AI mapper·route·intent 계약. +- 검증: home/modal focused test 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 초기 COMMUNITY/LIVE 동작은 Phase 13·16에서 승인된 목적지로 대체됐으며 AI characterId 표시와 creatorId 이동 계약은 분리되어 있다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | mapper·route·intent 검토 완료 | +| 후보 항목 판정 완료 | 충족 | 후속 Phase 변경 분리 완료 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | 자동 검증 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음. Phase 12의 최근 활동·AI 캐릭터 이동 항목은 사용자 확인으로 완료됐다. + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-final-verification-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-final-verification-review.md new file mode 100644 index 00000000..ac8701a5 --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-final-verification-review.md @@ -0,0 +1,136 @@ +# Phase 12 최종 검증·문서 기록 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | 첫 번째 Phase 12 / Task 12.1~12.3 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` 첫 번째 Phase 12 | +| 리뷰 상태 | 수정·수동 검증 완료, 최신 전체 1,198 tests 성공, 과거 test isolation 관찰 보류 | + +## 2. 리뷰 목적과 범위 + +- 목적: 자동 검증 완료 기록과 수동 확인 blocker가 실제 상태와 일치하는지 확인한다. +- 포함: Gradle test/compile/resource/ktlint, 문서 Verification Log, Task 12.2 상태. +- 제외: Codex가 직접 수행하지 않은 실제 UI 조작. Task 12.2 수동 항목은 사용자 확인 결과를 증거로 판정한다. + +## 3. 판정 기준 + +실행 결과의 재현 가능성, 실패 원인 분리, 미완료 수동 항목의 명시와 기존 기록 보존으로 판정했다. + +## 4. 검토한 근거 + +- 계획: Task 12.1~12.3 완료. Task 12.2는 2026-07-31 사용자 수동 확인 결과를 누적해 완료 상태다. +- 실행: home/modal focused test·resource merge·compile·ktlint 성공. 최신 `./gradlew :app:testDebugUnitTest --no-parallel --console=plain`은 162 suites, 1,198 tests 중 실패 0·error 0으로 성공했다. 과거 전체 suite 2회의 `CreatorChannelHomeViewModelTest` 2건/1건 실패와 해당 class 격리 19 tests 성공 기록은 `REV-P12-003` 관찰 이력으로 보존한다. +- 수동 검증: Codex 직접 기기 조작은 홈 추천 화면 접근 제약으로 완료하지 못했지만, 사용자가 Task 12.2의 홈 추천 화면 항목이 모두 동작함을 확인했다. + +## 5. 발견 사항 요약 + +| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | +|---|---|---|---|---|---| +| `REV-P12-001` | Low | 수정 완료 | Task 12.2의 기기 미연결 blocker가 현재 환경과 다르다 | `Task R12.1` | `P12-R1` | +| `REV-P12-002` | Low | 수정 완료 | Task 12.2가 제거된 first audio 표시 계약을 요구한다 | `Task R12.2` | `P12-R2` | +| `REV-P12-003` | Low | 보류 | 전체 unit suite에서 공유 `can` 상태 assertion이 비결정적으로 실패한다 | 별도 creator channel/preferences 테스트 격리 작업 | 범위 승인 후 정의 | + +## 6. 발견 사항 상세 + +### REV-P12-001 — Task 12.2의 기기 미연결 blocker가 현재 환경과 다르다 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** Task 12.2 수동 확인·리뷰 검증 상태 정확성 +- **소유 Task:** `Task R12.1` / `P12-R1` + +**관찰 내용·근거** + +`plan-task.md` Task 12.2는 `[blocked: 연결된 Android 기기 없음]`으로 남아 있지만 2026-07-31 재검사한 `adb devices`는 `device` 상태 기기 1대를 반환했다. 수동 항목은 아직 미실행이므로 Task 12.2를 완료로 판정할 수는 없으나 blocker 사유와 재개 조건은 갱신해야 한다. + +**영향·권장 조치** + +기능 결함은 확인되지 않았으나 현재 실행 가능한 검증을 외부 조건 대기로 남겨 Phase 종료 판정을 지연시킨다. 기기 연결 상태를 다시 기록하고 Task 12.2 수동 확인을 재개한다. + +**판정 기록** + +- 2026-07-31 — escalated `adb devices` 결과로 기존 미연결 blocker가 현재 상태와 다름을 확정했다. +- 2026-07-31 — `P12-R1` 실행으로 기기 연결, 설치, 런처 실행, 스크린샷을 재확인했고 blocker를 `SplashActivity` 검은 화면으로 갱신해 당시 상태를 정정했다. +- 2026-07-31 — 이후 사용자 수동 확인으로 Task 12.2 홈 추천 화면 항목이 모두 동작함을 확인해 현재 blocker는 해소됐다. + +전체 테스트 최초 실패는 이번 Phase 대상이 아닌 `CreatorChannelHomeViewModelTest`의 기존 비동기 preference 경쟁 조건으로 좁혀졌고 격리·전체 재실행이 성공해 현재 변경 회귀로 판정하지 않았다. + +### REV-P12-002 — Task 12.2가 제거된 first audio 표시 계약을 요구한다 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** `P12B-T1`·`P12B-T2` first audio production/UI/resource 제거 +- **소유 Task:** `Task R12.2` / `P12-R2` + +**관찰 내용·근거** + +두 번째 Phase 12(`P12B`)에서 first audio 섹션·DTO·adapter·resource를 제거했지만, 아직 미완료인 첫 번째 Phase 12의 Task 12.2 수동 확인 목록은 `첫 오디오 태그 조건`을 현재 표시 계약으로 유지했다. `rg`로 v2 home production/resource에 제거 대상 참조가 없고 해당 수동 항목만 활성 계약으로 남은 상태를 확인했다. + +**영향·권장 조치** + +실행하려는 수동 검증이 이미 제거된 UI를 요구해 항목 완료가 불가능하고 Phase 12B 제거 계약과도 모순된다. 과거 이력은 보존하고 Task 12.2의 활성 항목만 first audio 섹션과 관련 태그 미노출 확인으로 정정한다. + +**판정 기록** + +- 2026-07-31 — `plan-task.md` Task 12.2의 활성 항목과 Phase 12B 제거 Task·production 검색을 대조해 문서 정합성 문제로 확정했다. +- 2026-07-31 — `P12-R2`로 Task 12.2 활성 항목을 미노출 검증으로 정정하고 production 금지 참조 검색과 `git diff --check`를 통과해 수정 완료로 판정했다. + +### REV-P12-003 — 전체 unit suite에서 공유 `can` 상태 assertion이 비결정적으로 실패한다 + +- **심각도:** Low +- **상태:** 보류 +- **관련 요구사항:** Task 12.1 전체 `:app:testDebugUnitTest` 성공 검증 +- **소유 Task:** 현재 계획 범위 밖 `CreatorChannelHomeViewModelTest` / `SharedPreferenceManager` 테스트 격리 + +**관찰 내용** + +`./gradlew :app:testDebugUnitTest --no-parallel`을 두 차례 실행했을 때 첫 실행은 1,196개 중 `CreatorChannelHomeViewModelTest` 2건, 두 번째 실행은 동일 class 1건이 실패했다. 실패 테스트는 채널 후원 성공 후 `SharedPreferenceManager.can`이 `200 -> 150`, `30 -> 0`으로 차감되어야 하지만 각각 `200`, `30`으로 남은 assertion이다. 해당 class 19개를 격리 재실행하면 모두 통과했다. + +**근거** + +- 현재 diff에 `app/src/main/java/kr/co/vividnext/sodalive/v2/creator/channel`, `CreatorChannelHomeViewModelTest.kt`, `SharedPreferenceManager.kt`, `AppPreferencesDataStoreProvider.kt` 변경은 없다. +- `채널 후원 성공은 기존 후원 API를 호출하고 홈을 다시 로드한다` 테스트는 repository success callback 후 두 번째 `getHome()` 검증까지 통과했지만, 직후 `can` 값은 설정 전 값으로 관찰됐다. +- `SharedPreferenceManager` reset은 observer job을 cancel하지만 전역 `appScope(Dispatchers.IO)`와 DataStore write/collect를 사용하고, 전체 suite의 여러 test class가 같은 저장소를 init/reset한다. 이 구조는 격리 성공·전체 실패 패턴과 일치하지만, 결정적 재현 test와 수정 범위는 이번 홈 추천 리뷰에서 추가하지 않았다. + +**영향·권장 조치** + +홈 추천 focused 회귀는 green이지만 Task 12.1의 전체 suite를 현재 시점에서 안정적인 green gate로 사용할 수 없다. `CreatorChannelHomeViewModelTest`와 공유 preferences 테스트 격리를 별도 PRD/계획 범위로 승인하면, 순서와 관계없이 재현되는 실패 test를 먼저 고정한 뒤 최소 수정한다. + +**판정 기록** + +- 2026-07-31 — 전체 suite 2회 실패, 실패 class 격리 성공, 변경 범위 대조로 홈 추천 회귀와 분리했다. +- 2026-07-31 — 수정은 현재 문서의 Phase 범위를 넘고 공유 preferences 테스트 구조를 변경할 수 있어 보류했다. 사용자가 별도 범위를 승인하면 재현 test부터 재개한다. + +## 7. 확정 항목의 plan·goal 전환 + +`plan-task.md` 첫 번째 Phase 12의 `Task R12.1` / `P12-R1`은 기기 상태·blocker 정정과 이후 사용자 수동 확인까지, `Task R12.2` / `P12-R2`는 first audio 제거 후 수동 확인 기준 동기화까지 완료했다. `REV-P12-003`은 현재 Phase 범위 밖이므로 plan Task로 전환하지 않고, 비결정적 실패가 다시 재현될 때 별도 creator channel/preferences 테스트 격리 범위로 재개한다. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | 자동·문서·수동 상태 확인 | +| 후보 항목 판정 완료 | 충족 | `REV-P12-001`, `REV-P12-002` 수정 완료, `REV-P12-003` 보류 조건 기록 | +| 확정 항목 plan 반영 | 충족 | `Task R12.1` / `P12-R1`, `Task R12.2` / `P12-R2` 완료 | +| 보류 항목의 담당·재개 조건 기록 | 충족 | `REV-P12-003`은 비결정적 실패 재현 시 creator channel/preferences 테스트 격리 범위로 재개 | +| 검증 명령과 결과 기록 | 충족 | focused 성공·최신 전체 1,198 tests 성공·과거 격리 관찰을 Verification Log에 누적 | + +**최종 결론:** 자동·사용자 수동 검증 승인, 과거 test isolation 관찰 보류 + +**남은 항목:** 현재 Phase 수정 항목은 없음. `REV-P12-003`은 전체 suite에서 동일 실패가 다시 재현될 때 별도 테스트 격리 범위로 재개한다. + +## 9. 수정 후 검증 기록 + +- 2026-07-31 — `P7-R3` 실행형 회귀 테스트 보강 후 전체 `:app:testDebugUnitTest`를 재실행했고 162 suites, 1,198 tests, 실패 0, error 0으로 BUILD SUCCESSFUL을 확인했다. `REV-P12-003`의 과거 비결정적 관찰은 재현되지 않았으며 기존 재개 조건을 유지한다. +- 2026-07-31 — 현재 working tree에서 home/modal focused test·`:app:mergeDebugResources`·`:app:compileDebugKotlin`·`:app:ktlintCheck`를 함께 실행해 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`의 과거 비결정적 관찰을 삭제하지 않으며, 동일 실패 재현 시 재개 조건을 유지한다. +- 2026-07-31 — 사용자 수동 확인 결과 Task 12.2의 추천 tab·스크롤·섹션·라우팅·인기 커뮤니티 상태·사업자 정보 항목이 모두 동작함을 확인해 이전 화면 접근 blocker를 해소된 이력으로 갱신했다. +- 2026-07-31 — Phase별 리뷰 보고서와 계획 문서 반영 후 `./gradlew tasks --all --console=plain`을 실행해 BUILD SUCCESSFUL을 확인했다. `reviews/`의 Phase 1~17 및 별도 이력 Phase 12B 보고서 존재 여부, 확정 지적과 신규 회귀 Task의 연결, `git diff --check`를 다시 확인했다. +- 2026-07-31 — `Task R12.1` 실행으로 `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 수동 항목은 홈 추천 화면 접근 불가로 계속 blocked다. +- 2026-07-31 — 수동 Gate 재검증에서 `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 화면으로 판독됐다. Task 12.2 수동 항목은 홈 추천 화면 접근 불가로 계속 blocked다. +- 2026-07-31 — `Task R12.2` 실행으로 Task 12.2의 `첫 오디오 태그 조건`을 `Phase 12B에서 제거된 첫 오디오 섹션과 관련 태그 미노출`로 정정했다. 과거 검증 이력은 보존했고 v2 home production/resource의 제거 대상 참조가 없음을 `rg`로 확인했으며 `git diff --check`는 출력 없이 성공했다. 수동 확인은 기존 `SplashActivity` 검은 화면 blocker로 인해 계속 미완료다. +- 2026-07-31 — 현재 working tree에서 `./gradlew :app:testDebugUnitTest --no-parallel`을 두 차례 실행했고 1,196개 중 `CreatorChannelHomeViewModelTest` 2건/1건 실패로 모두 `BUILD FAILED`를 확인했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.creator.channel.CreatorChannelHomeViewModelTest" --no-parallel`은 19개 모두 통과했다. 실패는 현재 diff 범위 밖의 공유 preferences 비동기 상태와 일치해 `REV-P12-003` 보류로 기록했고 해당 코드는 변경하지 않았다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-first-audio-removal-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-first-audio-removal-review.md new file mode 100644 index 00000000..bab34c8c --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase12-first-audio-removal-review.md @@ -0,0 +1,123 @@ +# Phase 12B first audio 제거 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | 두 번째 Phase 12 / 역사적 Task 10.1~10.2 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` 두 번째 Phase 12 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: first audio 제거 완료 여부와 Phase/Task 식별자의 추적 가능성을 확인한다. +- 포함: recommendation DTO/model/state/Fragment, content overview, string/layout/adapter 참조와 계획 문서 식별자. +- 제외: `NEW_AND_HOT_AUDIO` 등 유지 대상 오디오 기능. + +## 3. 판정 기준 + +production 참조 완전 제거, 유지 대상 비회귀, Phase·Task·Goal의 고유 추적 가능성으로 판정했다. + +## 4. 검토한 근거 + +- 코드 검색: `app/src/main`에서 `firstAudioContents`, `HomeFirstAudio`, `FIRST_AUDIO_CONTENT`, 제거 string/layout production 참조 확인. +- 문서: `plan-task.md`의 두 `Phase 12` 제목과 두 번째 Phase의 `Task 10.1`·`Task 10.2`. +- 검증: home/content overview focused 검증 기록, 현재 home/modal focused test 성공 및 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | +|---|---|---|---|---|---| +| `REV-P12B-001` | Low | 수정 완료 | Phase와 Task 식별자가 다른 완료 항목과 중복된다 | `Task R12B.1` | `P12B-R1` | +| `REV-P12B-002` | Low | 수정 완료 | 제거된 first audio가 PRD 활성 계약에 남아 있다 | `Task R12B.2` | `P12B-R2` | + +## 6. 발견 사항 상세 + +### REV-P12B-001 — Phase와 Task 식별자가 다른 완료 항목과 중복된다 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** first audio 제거 자체에는 위반 없음 +- **관련 계약:** `work-plan-docs.md` Goal/Task 추적성 규칙 +- **소유 Task:** `Task R12B.1` / `P12B-R1` + +**관찰 내용** + +문서에 `Phase 12`가 두 번 있고, second Phase 12의 완료 항목은 `Task 10.1`·`Task 10.2`로 기록되어 Phase 10 및 첫 번째 Phase 12와 참조가 충돌한다. 실제 first audio 제거는 완료됐지만 리뷰 보고서와 후속 회귀 Task에서 소유 Phase를 고유하게 지정할 수 없다. + +**근거** + +- 문서: `plan-task.md`의 `Phase 12: 최종 검증과 문서 기록`과 `Phase 12: 처음부터 함께 성장! 섹션 제거` 제목. +- 문서: 두 번째 Phase 12 안의 `Task 10.1`·`Task 10.2`. +- 코드: 제거 대상 production 참조가 없어 기능 결함은 확인되지 않았다. + +**재현 또는 검증 절차** + +1. `rg -n '^### Phase 12|Task 10\\.' plan-task.md`를 실행한다. +2. 서로 다른 결과가 같은 Phase/Task 식별자를 공유함을 확인한다. +3. first audio production 참조 제거 여부를 별도 검색해 기능 완료와 문서 추적성 문제를 분리한다. + +**영향** + +런타임 영향은 없지만 향후 리뷰, 회귀 수정, goal 완료 증거가 잘못된 Phase에 연결될 수 있다. + +**권장 조치** + +기존 완료 기록을 재작성하지 않고 두 번째 Phase 12와 하위 Task에 `P12B` 계열 고유 별칭 대응표를 추가하고 이후 참조에 사용한다. + +**판정 기록** + +- 2026-07-31 — 문서 검색과 current tree 대조로 추적성 문제를 확정했다. +- 2026-07-31 — `P12B-R1` 별칭 표·이후 참조 규칙 추가와 `rg`·`git diff --check` 성공으로 수정 완료 판정했다. + +### REV-P12B-002 — 제거된 first audio가 PRD 활성 계약에 남아 있다 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** 2026-06-29 `firstAudioContents`·`HomeFirstAudioContentItem` 제거 +- **소유 Task:** `Task R12B.2` / `P12B-R2` + +**관찰 내용** + +production에서 first audio 섹션·adapter·resource는 제거됐지만 PRD의 현재 Problem, 섹션 구성 도식, Metrics, Open Questions에 `첫 오디오 콘텐츠`, `firstAudioContents`, `AudioContentCardView`, `releaseDate` 계약이 아직 남아 있다. + +**근거·재현** + +1. `rg -n "firstAudioContents|HomeFirstAudio|FIRST_AUDIO_CONTENT|AudioContentCardView|releaseDate" prd.md`는 PRD 활성 영역의 잔존 참조를 반환한다. +2. 동일 패턴을 `app/src/main`에서 검색하면 제거 대상 production 참조가 없다. +3. PRD의 `2026-06-29 변경`은 해당 필드·UI·resource 제거를 명시한다. + +**영향·권장 조치** + +런타임 결함은 없지만 요구사항과 Metrics가 이미 삭제된 기능을 현재 완료 조건으로 제시한다. 과거 이력은 보존하고 PRD 활성 영역만 Phase 12B 계약에 맞게 정정한다. + +**판정 기록** + +- 2026-07-31 — PRD 활성 영역과 production 금지 참조 검색으로 문서 정합성 문제를 확정했다. +- 2026-07-31 — `P12B-R2` PRD 활성 계약 정정과 production/resource 참조 검색, `git diff --check` 성공으로 수정 완료 판정했다. + +## 7. 확정 항목의 plan·goal 전환 + +`Task R12B.1` / `P12B-R1`과 `Task R12B.2` / `P12B-R2`는 수정 검증까지 완료했다. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | 제거 코드·문서 식별자 검토 완료 | +| 후보 항목 판정 완료 | 충족 | `REV-P12B-001`, `REV-P12B-002` 수정 완료 | +| 확정 항목 plan 반영 | 충족 | `Task R12B.2` / `P12B-R2` 완료 | +| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 없음 | +| 검증 명령과 결과 기록 | 충족 | Verification Log 연결 | + +**최종 결론:** 승인 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +- 2026-07-31 — `P12B-R1` 실행으로 두 번째 `Phase 12`와 하위 `Task 10.1`·`Task 10.2`에 고유 별칭을 추가했다. 변경 파일은 `plan-task.md`와 이 리뷰 보고서다. 중복 재현은 `rg -n "^(### Phase 12:|## Phase 12:|#### Task 10\.|- \[x\] \*\*Task 10\.)" "docs/20260601_메인_홈_추천_UI와_API_연동/plan-task.md"`로 확인했고, 기존 완료 제목과 체크박스는 보존한 채 `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`는 출력 없이 성공했다. 남은 항목은 없다. +- 2026-07-31 — `P12B-R2` 실행으로 PRD 활성 Problem, 섹션 도식, Metrics, Open Questions에서 제거된 first audio 완료 조건을 정리했다. 2026-06-01 과거 기록과 2026-06-29 제거 기록은 보존했다. `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 과거 기록 및 제거 기록만 반환하고 v2 home production/resource 제거 대상 참조는 반환하지 않았다. `git diff --check`는 출력 없이 성공했다. 남은 항목은 없다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase13-community-detail-routing-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase13-community-detail-routing-review.md new file mode 100644 index 00000000..c120c804 --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase13-community-detail-routing-review.md @@ -0,0 +1,58 @@ +# Phase 13 커뮤니티 상세 라우팅 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 13 / Task 13.1~13.2 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 13 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: 최근 활동 COMMUNITY와 인기 커뮤니티 item이 `postId` 기반 상세 화면으로 이동하는지 확인한다. +- 포함: route·공통 `CommunityAction` 연결, Fragment login guard, 기존 목록 route 제거와 tests. +- 제외: 상세 화면 내부 기능과 API 조회. + +## 3. 판정 기준 + +유효 `postId` 전달, invalid 값 no-op, 로그인 가드 순서, 기존 `CreatorCommunityAllActivity` extra 제거로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeRecommendationUiModels.kt`, `HomeMainFragment.kt` 커뮤니티 callback. +- 테스트: `HomeMainFragmentLayoutTest`, `HomeMainFragmentLoginGuardSourceTest`. +- 검색: v2 home production의 이전 커뮤니티 전체 목록 route 잔존 여부. +- 검증: home/modal focused test·compile·ktlint 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 두 진입점 모두 현재 `postId`를 `CommunityActionCommand.PostDetail`로 전달하고 공통 handler가 `CreatorChannelCommunityDetailActivity`로 이동하며 invalid 값은 이동하지 않는다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | route·guard·금지 참조 검토 완료 | +| 후보 항목 판정 완료 | 충족 | 확정 후보 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | focused 검증 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음. Phase 12 관련 수동 항목은 사용자 확인으로 완료됐다. + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase14-genre-visibility-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase14-genre-visibility-review.md new file mode 100644 index 00000000..7dd52038 --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase14-genre-visibility-review.md @@ -0,0 +1,57 @@ +# Phase 14 장르 크리에이터 미표시 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 14 / genreCreators UI 미표시 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 14 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: 응답·mapper 계약은 유지하면서 추천 탭에서 genre section과 follow-all 동작이 노출되지 않는지 확인한다. +- 포함: Fragment visibility/binding, `HomeRecommendationUiState.isEmpty`, ViewModel follow 상태와 tests. +- 제외: 장르 섹션 재도입 설계. + +## 3. 판정 기준 + +항상 `GONE`, hidden section 기준 empty 오판 방지, genre follow callback 미연결, 응답 파싱 유지로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeMainFragment.bindGenreCreatorSection()`, `HomeRecommendationUiState.kt`, recommendation mapper. +- 테스트: `HomeMainFragmentLayoutTest`의 hidden genre/empty 계약. +- 검증: home/modal focused test 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 숨겨진 genre data만 존재하는 경우도 Empty로 판정하며 Fragment가 해당 섹션을 노출하거나 모두 팔로우를 연결하지 않는다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | UI·state·mapper 대조 완료 | +| 후보 항목 판정 완료 | 충족 | 확정 후보 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | 자동 검증 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase15-section-chevron-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase15-section-chevron-review.md new file mode 100644 index 00000000..6774f681 --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase15-section-chevron-review.md @@ -0,0 +1,57 @@ +# Phase 15 추천 섹션 chevron 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 15 / 추천 섹션 타이틀 chevron 숨김 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 15 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: 홈 추천의 최근 데뷔와 AI 캐릭터 섹션 제목에 더보기 chevron이 노출되지 않는지 확인한다. +- 포함: `HomeMainFragment.setUpSectionTitles()`와 source/layout tests. +- 제외: 다른 홈 tab과 섹션 전체보기 기능 설계. + +## 3. 판정 기준 + +대상 두 제목의 `showMore` 미지정과 다른 tab/section에 대한 비의도 변경 없음으로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeMainFragment.setUpSectionTitles()`. +- 테스트: `HomeMainFragmentSourceTest`, `HomeMainFragmentLayoutTest`, `HomeFollowingFragmentSourceTest`. +- 검증: home/modal focused test·compile/resource/ktlint 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 대상 제목에 chevron 활성 계약이 남아 있지 않고 다른 홈 tab source test도 통과한다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | source와 test 대조 완료 | +| 후보 항목 판정 완료 | 충족 | 확정 후보 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | focused 검증 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +수정 항목 없음. 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 index d7e55dd1..d7d81ff5 100644 --- 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 @@ -140,8 +140,16 @@ Phase 16 요구사항 위반, 공통 접근 정책 우회, 다른 activity type **최종 결론:** 확정 발견 사항 없음. -**남은 항목:** Phase 16 수정 항목은 없다. 전체 unit suite는 범위 밖의 비결정적 `v2.creator.channel` 실패로 완전한 green 상태가 아니며, 해당 영역을 별도 리뷰할 때 테스트 격리성과 `SharedPreferenceManager` 전역 상태 사용을 재현 조건으로 점검할 수 있다. +**남은 항목:** Phase 16 수정 항목은 없다. 이전 `v2.creator.channel` 비결정적 실패는 관찰 이력으로 보존하고, 동일 실패 재현 시 별도 테스트 격리 범위로 재개한다. ## 9. 수정 후 검증 기록 수정 항목이 없어 해당 없음. + +## 10. 2026-07-31 전체 계획 기준 재검토 + +- 기준: `a106548f` 기반 현재 working tree와 `plan-task.md` 전체 Phase를 다시 대조했다. +- 범위: LIVE route의 `targetId` 우선, null일 때만 `creatorId` fallback, invalid ID no-op, 공통 Live/Creator action 경로와 기존 activity type 회귀. +- 검증: v2 home 및 `V2ModalDialog` focused test·resource merge·compile·ktlint가 성공했고, 최신 전체 1,198 tests는 실패 0·error 0으로 성공했다. +- 판정: Phase 16 확정 발견 사항 없음. 신규 회귀 Task 전환 항목 없음. +- 참고: 전체 테스트 최초 실행의 범위 밖 `CreatorChannelHomeViewModelTest` 1건 실패는 격리 및 전체 재실행에서 통과해 기존 보고서의 비결정적 suite 관찰과 같은 범주로 유지한다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase17-follow-all-confirmation-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase17-follow-all-confirmation-review.md new file mode 100644 index 00000000..3e40e1b2 --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase17-follow-all-confirmation-review.md @@ -0,0 +1,113 @@ +# Phase 17 모두 팔로우 확인 절차 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 17 / Task 17.1, P17-GATE | +| 기준 commit 또는 working tree | `a106548f` 기반 Phase 17 미커밋 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 17 | +| 리뷰 상태 | 수정 검증 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: 응원 크리에이터 모두 팔로우가 확인 전 요청하지 않고 취소 no-op, 확인 후 단일 요청과 다국어 계약을 지키는지 확인한다. +- 포함: `HomeMainFragment`, `HomeCheerCreatorAdapter`, `HomeFollowAllButtonBinder`, `V2ModalDialog`, 세 locale resource와 tests. +- 제외: 모두 팔로우 API/ViewModel 계약 변경, 숨겨진 genre UI, 공통 Dialog 리팩터링. + +## 3. 판정 기준 + +Dialog 선행, confirm callback 단독 API 호출, cancel dismiss-only, 중복 confirm 방지, 완료·빈 목록 no-op, locale resource 분리로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeMainFragment.showCheerCreatorsFollowAllConfirmDialog()`, `HomeFollowAllButtonBinder`, `V2ModalDialog`. +- 리소스: `values`, `values-en`, `values-ja`의 `home_recommendation_follow_all_confirm_*`. +- 테스트: `HomeMainFragmentLayoutTest`, `V2ModalDialogTest`. +- 검증: home/modal focused test·resource merge·compile·ktlint 성공. 최신 전체 1,198 tests 중 실패 0·error 0. +- 수동 검증: 사용자 수동 확인으로 P17-GATE의 시각·상호작용 항목은 동작 확인. 추가 발견된 미로그인 로그인 가드 순서 회귀는 `P17-R2`로 수정. + +## 5. 발견 사항 요약 + +| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | +|---|---|---|---|---|---| +| `REV-P17-001` | Low | 수정 완료 | 신규 모두 팔로우 테스트명 2건이 영문이다 | `Task R17.1` | `P17-R1` | +| `REV-P17-002` | Medium | 수정 완료 | 모두 팔로우 미로그인 터치에서 확인 Dialog가 로그인 가드보다 먼저 표시된다 | `Task R17.2` | `P17-R2` | + +사용자 수동 확인과 `P17-R2` 회귀 수정 후 Phase 17 기능 계약은 충족한다. + +## 6. 발견 사항 상세 + +### REV-P17-001 — 신규 모두 팔로우 테스트명 2건이 영문이다 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** `code-style.md` 신규 Kotlin 테스트명 한글 문장 규칙 +- **소유 Task:** `Task R17.1` / `P17-R1` + +**관찰 내용·근거** + +Phase 17에서 신규 추가한 `cheer creator follow all opens confirm dialog before api request`, `follow all confirm strings are localized` 테스트명이 영문이고 한글 검증 의도 주석도 없다. Dialog 선행·다국어 assertion 자체는 focused test에서 성공했다. + +**재현 절차** + +1. current diff에서 위 두 신규 `fun \`...\`` 메서드를 확인한다. +2. `code-style.md` §5 테스트 관례와 대조한다. + +**영향·권장 조치** + +런타임 영향은 없으나 신규 테스트가 저장소 표현 규칙과 다르다. assertion은 바꾸지 않고 두 테스트명만 한글 문장으로 정정한다. + +**판정 기록** + +- 2026-07-31 — current diff와 테스트 네이밍 규칙 대조로 확정했다. +- 2026-07-31 — `P17-R1` 테스트명 정정과 direct 영향 회귀, `ktlintCheck`, `git diff --check` 성공으로 수정 완료 판정했다. + +기능 판정은 변경 없음. `onCheerFollowAllClick`은 Dialog helper만 호출하고 `confirmButtonClick`에서만 기존 section key와 creator ID 목록으로 ViewModel을 호출한다. 취소 callback은 전달하지 않아 공통 Dialog의 dismiss-only 동작을 사용한다. + +### REV-P17-002 — 모두 팔로우 미로그인 터치에서 확인 Dialog가 로그인 가드보다 먼저 표시된다 + +- **심각도:** Medium +- **상태:** 수정 완료 +- **관련 요구사항:** 기존 MainV2 로그인 가드 우선 정책, `P17-T1` 확인 전 요청 금지 +- **소유 Task:** `Task R17.2` / `P17-R2` + +**관찰 내용·근거** + +사용자 수동 확인에서 `모두 팔로우` 터치 시 미로그인 로그인 가드보다 `V2ModalDialog` 확인창이 먼저 표시되는 버그가 확인됐다. 현재 `onCheerFollowAllClick`은 `showCheerCreatorsFollowAllConfirmDialog(creatorIds)`를 직접 호출한다. + +**영향·권장 조치** + +미로그인 사용자에게 follow 확인 UI를 먼저 보여 기존 접근 정책과 맞지 않는다. 신규 공통 abstraction 없이 기존 `ensureV2Access(AccessRequirement.Login)`를 Dialog 표시 전에 적용한다. + +**판정 기록** + +- 2026-07-31 — 사용자 수동 확인으로 로그인 가드 순서 회귀를 확정했다. +- 2026-07-31 — `P17-R2` RED/GREEN과 직접 영향 회귀, compile, ktlint, `git diff --check` 성공으로 수정 완료 판정했다. + +## 7. 확정 항목의 plan·goal 전환 + +`plan-task.md` Phase 17의 `Task R17.1` / `P17-R1`과 `Task R17.2` / `P17-R2`는 수정 검증까지 완료했다. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | 코드·resource·test·Gate 상태 대조 완료 | +| 후보 항목 판정 완료 | 충족 | `REV-P17-001`, `REV-P17-002` 수정 완료 | +| 확정 항목 plan 반영 | 충족 | `Task R17.1` / `P17-R1`, `Task R17.2` / `P17-R2` 완료 | +| 보류 항목의 담당·재개 조건 기록 | 충족 | 보류 없음 | +| 검증 명령과 결과 기록 | 충족 | focused/full 검증 기록 연결 | + +**최종 결론:** 승인 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +- 2026-07-31 — `Task R17.1` 실행으로 신규 영문 테스트명 2건을 `응원 크리에이터 모두 팔로우는 API 요청 전에 확인창을 연다`, `모두 팔로우 확인창 문자열은 다국어로 제공한다`로 정정했다. Production Dialog·ViewModel·API 동작과 assertion은 변경하지 않았다. `HomeMainFragmentLayoutTest`·`V2ModalDialogTest`, `./gradlew :app:ktlintCheck`, `git diff --check`가 모두 성공했다. 당시 `P17-GATE`의 실제 확인창 시각·상호작용 4항목은 정상 화면 캡처·조작이 가능한 기기 환경에서 재개하는 상태였다. +- 2026-07-31 — 수동 Gate 재검증에서 `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 화면으로 판독됐다. 당시 실제 확인창 시각·상호작용 4항목은 홈 추천 화면 접근 불가로 blocked였다. +- 2026-07-31 — 사용자 수동 확인에서 Task 12.2와 P17-GATE 수동 항목은 모두 동작함을 확인했지만, 모두 팔로우 미로그인 터치 시 로그인 가드보다 확인 Dialog가 먼저 표시되는 회귀가 발견되어 `REV-P17-002` / `P17-R2`로 전환했다. +- 2026-07-31 — `Task R17.2` 실행으로 `onCheerFollowAllClick`에서 기존 `ensureV2Access(AccessRequirement.Login)` 통과 후 `showCheerCreatorsFollowAllConfirmDialog(creatorIds)`를 호출하도록 수정했다. RED는 `HomeMainFragmentLoginGuardSourceTest.HomeMainFragment 응원 크리에이터 모두 팔로우는 확인창보다 로그인 가드를 먼저 실행한다`와 `HomeMainFragmentLayoutTest.응원 크리에이터 모두 팔로우는 API 요청 전에 확인창을 연다` focused test 실패로 확인했고, GREEN 후 동일 focused test는 성공했다. 직접 영향 회귀 `HomeMainFragmentLoginGuardSourceTest`, `HomeMainFragmentLayoutTest`, `V2ModalDialogTest`, `compileDebugKotlin`, `ktlintCheck`, `git diff --check`도 모두 성공했다. `ktlintCheck`의 `.editorconfig disabled_rules` deprecation warning과 Gradle deprecation warning은 기존 경고다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase2-recommendation-data-layer-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase2-recommendation-data-layer-review.md new file mode 100644 index 00000000..32259adb --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase2-recommendation-data-layer-review.md @@ -0,0 +1,58 @@ +# Phase 2 추천 데이터 계층 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 2 / API DTO, Repository, DI 추가 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 2 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: 추천 조회·모두 팔로우 계약과 DI 연결이 PRD 범위에 맞는지 확인한다. +- 포함: `HomeRecommendationApi`, DTO, Repository, `AppDI` 등록. +- 제외: 서버 실제 응답과 네트워크 E2E. + +## 3. 판정 기준 + +API path/method, request 필드, auth header 전달, repository 책임, v2 패키지 경계를 기준으로 판정했다. + +## 4. 검토한 근거 + +- 코드: `v2/main/home/data/HomeRecommendationApi.kt`, `HomeRecommendationModels.kt`, `HomeRecommendationRepository.kt`, `di/AppDI.kt`. +- 테스트: `HomeRecommendationViewModelTest`, home source/layout tests. +- 검증: home/modal focused test 및 compile/resource/ktlint 성공, 최신 전체 1,198 tests 중 실패 0·error 0. +- 환경: 실제 서버 호출 없음. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 후속 응답 스키마 변경과 first audio 제거가 현재 DTO·mapper에 반영되어 초기 Phase 계약과의 역사적 차이는 후속 승인 변경으로 확인했다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | API·DTO·Repository·DI 대조 완료 | +| 후보 항목 판정 완료 | 충족 | 후속 계약 변경 이력 확인 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | `plan-task.md` Verification Log 연결 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 실제 서버 E2E는 이번 리뷰 범위 밖이다. + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase3-activity-type-mapper-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase3-activity-type-mapper-review.md new file mode 100644 index 00000000..5177ef5e --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase3-activity-type-mapper-review.md @@ -0,0 +1,57 @@ +# Phase 3 활동 타입·mapper 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 3 / 순수 mapper와 unit test 작성 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 3 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: 추천 응답의 UI model 변환과 활동 타입 다국어 fallback 계약을 확인한다. +- 포함: `CreatorActivityType`, 추천 mapper/UI model, 문자열 리소스와 mapper 테스트. +- 제외: 카드 레이아웃과 클릭 라우팅. + +## 3. 판정 기준 + +null/unknown 안전성, 필드 보존, locale resource 분리, 후속 공용 타입 전환 시 회귀 여부로 판정했다. + +## 4. 검토한 근거 + +- 코드: `v2/common/CreatorActivityType.kt`, `HomeRecommendationMappers.kt`, `HomeRecommendationUiModels.kt`. +- 테스트: `HomeCreatorActivityTypeLabelResTest`, `HomeMainFragmentLayoutTest` mapper 계약. +- 검증: home/modal focused test 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 계획의 초기 `RecommendedActivityType` 명칭은 후속 commit에서 공용 `CreatorActivityType`으로 전환됐고 production과 테스트가 함께 갱신되어 현재 결함이 아니다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | mapper·공용 타입·다국어 대조 완료 | +| 후보 항목 판정 완료 | 충족 | 명칭 변경의 후속 이력 확인 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | 자동 테스트 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase4-recommendation-state-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase4-recommendation-state-review.md new file mode 100644 index 00000000..3c5662f9 --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase4-recommendation-state-review.md @@ -0,0 +1,57 @@ +# Phase 4 추천 상태·ViewModel 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 4 / ViewModel과 화면 상태 구성 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 4 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: loading/content/empty/error와 모두 팔로우 상태 갱신이 계획과 일치하는지 확인한다. +- 포함: `HomeRecommendationUiState`, `HomeRecommendationViewModel`, DI와 unit test. +- 제외: Fragment 표시와 실제 네트워크 E2E. + +## 3. 판정 기준 + +상태 전이, 성공 여부 판정, 표시하지 않는 genre 제외 empty 정책, 오류·토스트 처리와 기존 API 계약 유지 여부로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeRecommendationUiState.kt`, `HomeRecommendationViewModel.kt`, `AppDI.kt`. +- 테스트: `HomeRecommendationViewModelTest`, `HomeMainFragmentLayoutTest`의 empty/mapper 계약. +- 검증: 관련 focused test 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 숨겨진 genre를 empty 판정에서 제외하는 후속 Phase 14 정책과 Phase 17의 Dialog 선행 절차가 현재 상태 계층과 충돌하지 않는다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | 상태·ViewModel·DI 검토 완료 | +| 후보 항목 판정 완료 | 충족 | 후속 정책 포함 판정 완료 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | focused/full test 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase5-home-shell-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase5-home-shell-review.md new file mode 100644 index 00000000..42170c48 --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase5-home-shell-review.md @@ -0,0 +1,57 @@ +# Phase 5 홈 shell·상단 UI 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 5 / HomeMainFragment 레이아웃과 상단 UI 구현 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 5 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: title bar, text tab bar, 추천 content scroll shell과 섹션 container 구성이 유지되는지 확인한다. +- 포함: `fragment_v2_main_home.xml`, `HomeMainFragment`, title/tab/section source tests. +- 제외: Figma 실기기 pixel 비교와 후속 섹션 내부 상세. + +## 3. 판정 기준 + +추천 tab 선택, swipe 미사용, 고정 상단과 하단 content 스크롤 분리, 섹션 기본 visibility를 기준으로 판정했다. + +## 4. 검토한 근거 + +- 코드: `fragment_v2_main_home.xml`, `HomeMainFragment.onViewCreated()`의 tab bar 설정, `setUpSectionTitles()`. +- 테스트: `HomeMainFragmentLayoutTest`, `HomeMainFragmentSourceTest`, `HomeMainTitleBarActionSourceTest`. +- 검증: home/modal focused test·resource merge·compile·ktlint 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 현재 Fragment 경로는 후속 패키지 정리 결과이며 레이아웃 binding과 source test가 현 경로를 기준으로 통과한다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | XML·Fragment·source test 대조 완료 | +| 후보 항목 판정 완료 | 충족 | 확정 후보 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | merge/compile/test/ktlint 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음. Phase 12 관련 수동 항목은 사용자 확인으로 완료됐다. + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase6-recommendation-sections-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase6-recommendation-sections-review.md new file mode 100644 index 00000000..d54cda5e --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase6-recommendation-sections-review.md @@ -0,0 +1,57 @@ +# Phase 6 추천 섹션 adapter·widget 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 6 / Task 6.1~6.13 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 6 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: 각 추천 섹션 adapter/binder, 빈 목록 숨김, Figma 후속 조정과 공용 widget 재사용을 확인한다. +- 포함: `v2/main/home/ui`, 홈 레이아웃, `HomeMainFragment` section binding. +- 제외: 인기 커뮤니티 상세는 Phase 7, first audio 제거와 genre 숨김은 후속 Phase에서 별도 판정. + +## 3. 판정 기준 + +섹션별 단일 책임, 기존 widget 재사용, 빈 목록 `GONE`, 표시 개수·간격·클릭 callback, 후속 승인 변경 반영으로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeLiveAdapter`, `HomeRecentActivityCreatorAdapter`, `HomeRecentDebutCreatorAdapter`, `HomeAiCharacterAdapter`, `HomeCheerCreatorAdapter`, 관련 binder. +- 테스트: `HomeMainFragmentLayoutTest`, `HomeCreatorProfileImageLoaderTest`. +- 검증: home/modal focused test·resource merge·compile·ktlint 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. first audio와 genre의 초기 구현 기록은 Phase 12B와 Phase 14의 승인된 후속 변경으로 현재 UI에서 제거·숨김 처리되어 있다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | 섹션 adapter/binder와 후속 변경 대조 완료 | +| 후보 항목 판정 완료 | 충족 | 후속 Phase 소유 항목 분리 완료 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | focused 검증 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음. Phase 12 관련 수동 항목은 사용자 확인으로 완료됐다. + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase7-popular-community-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase7-popular-community-review.md new file mode 100644 index 00000000..338b9fdf --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase7-popular-community-review.md @@ -0,0 +1,149 @@ +# Phase 7 인기 커뮤니티 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 7 / Task 7.1~7.5 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 7 | +| 리뷰 상태 | 수정 검증 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: 추천 커뮤니티의 데이터 보존, 이미지·유료 상태, 동적 비율과 검증 기록 정합성을 확인한다. +- 포함: `FeedItem.Community`, `FeedCommunityView`, `HomePopularCommunityAdapter`, mapper, 관련 XML/test/문서. +- 제외: 커뮤니티 상세 화면 자체와 서버 구매 권한 판정. + +## 3. 판정 기준 + +PRD의 시각 계약과 이후 보안 재리뷰 결정, 현재 코드·회귀 테스트, 변경 이력을 함께 대조하고 유료 원본의 네트워크·cache 노출 가능성을 기준으로 판정했다. + +## 4. 검토한 근거 + +- 요구사항: `prd.md` FeedCommunityView Requirements와 UX의 유료 미구매 blur/lock 항목. +- 코드: `HomePopularCommunityAdapter.kt:53-79`, `FeedCommunityView.kt`, `FeedItem.kt`. +- 테스트: `HomePopularCommunityAdapterTest`, `HomeMainFragmentLayoutTest`, `FeedViewTest`. +- 이력: commit `2d58a876`에서 원본 미로드 방식을 blur 변환 로드 방식으로 변경. +- 후속 승인 계약: `docs/20260712_v2_크리에이터_커뮤니티_게시글_이미지_원본비율/` 문서와 commit `9244a007`이 홈 추천을 포함한 커뮤니티 이미지를 원본 비율·`fitCenter`로 변경했다. 따라서 Phase 7의 초기 `346:236` 비율과 다른 현재 이미지 크기 계산은 결함으로 판정하지 않는다. +- 검증: `HomePopularCommunityAdapterTest`·home focused test·compile·ktlint 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | +|---|---|---|---|---|---| +| `REV-P7-001` | High | 수정 완료 | 유료 미구매 원본 이미지 로드가 다시 도입됐다 | `Task R7.1` | `P7-R1` | +| `REV-P7-002` | Low | 수정 완료 | 회귀 수정에서 변경한 테스트명이 영문이다 | `Task R7.2` | `P7-R2` | +| `REV-P7-003` | Medium | 수정 완료 | 원본 미로드 회귀 테스트가 adapter bind 동작을 검증하지 않는다 | `Task R7.3` | `P7-R3` | + +## 6. 발견 사항 상세 + +### REV-P7-001 — 유료 미구매 원본 이미지 로드가 다시 도입됐다 + +- **심각도:** High +- **상태:** 수정 완료 +- **관련 요구사항:** 유료 미구매 커뮤니티 이미지 blur/lock 표시 +- **관련 계약:** 별도 API Contract 없음 +- **소유 Task:** `Task R7.1` / `P7-R1` + +**관찰 내용** + +2026-06-05 보안 재리뷰에서 유료 미구매 item의 원본 `imageUrl`을 로드하지 않고 기존 Coil 요청과 drawable을 비우도록 수정했다. commit `2d58a876`에서 이 원본 URL을 다시 로드한 뒤 client-side `BlurTransformation`을 적용하도록 바뀌었다. 현재 working tree에서는 `P7-R1` 수정으로 원본 미로드 계약이 복구됐다. + +**근거** + +- 수정 전 코드: commit `2d58a876`의 `HomePopularCommunityAdapter.kt`는 `imageUrl`을 로드하고 잠금 상태에서 blur를 적용한다. +- 현재 코드: `HomePopularCommunityAdapter.kt`는 잠금 유료 게시물의 `imageUrl`을 image loader에 전달하지 않는다. +- 문서: `prd.md` 2026-06-05 이력과 `plan-task.md`의 보안 재리뷰 기록은 원본 미로드와 기존 Coil 요청 취소를 성공 결과로 적는다. +- 변경 이력: commit `2d58a876`이 원본 미로드 구현과 회귀 테스트를 client-side blur 로드 방식으로 함께 변경했다. + +**재현 또는 검증 절차** + +1. PRD 현재 요구사항과 2026-06-05 변경 이력을 대조한다. +2. adapter의 `imageUrl`/`BlurTransformation`과 회귀 테스트를 확인한다. +3. `git show 2d58a876 -- HomePopularCommunityAdapter.kt HomeMainFragmentLayoutTest.kt`로 후속 정책 변경을 확인한다. +4. 보안 재리뷰에서 제거한 원본 로드가 후속 승인 기록 없이 다시 도입됐음을 확인한다. + +**영향** + +유료 미구매 원본 URL이 client network 요청과 image cache에 전달되므로 화면 blur와 무관하게 원본 콘텐츠 접근 가능성이 생긴다. 또한 현재 PRD의 시각 blur 문구와 보안 결정이 충돌해 후속 구현 판단도 불안정하다. + +**권장 조치** + +원본 미로드를 검증하는 실패 테스트를 먼저 복구하고 adapter를 최소 수정한다. 무료·구매 완료 이미지는 기존대로 유지하고, 유료 미구매 item은 원본 요청을 취소·비우되 lock overlay와 가격을 유지한다. PRD도 이 보안 결정과 일치시키며 서버가 별도 안전 preview를 제공한다는 계약은 추정하지 않는다. + +**판정 기록** + +- 2026-07-31 — 현재 코드·테스트·git 이력과 2026-06-05 보안 재리뷰 기록을 교차 확인해 보안 정책 회귀로 확정했다. +- 2026-07-31 — `P7-R1` 수정과 focused·home·compile·ktlint·diff 검증 성공으로 수정 완료 판정했다. + +### REV-P7-002 — 회귀 수정에서 변경한 테스트명이 영문이다 + +- **심각도:** Low +- **상태:** 수정 완료 +- **관련 요구사항:** `code-style.md` 신규 Kotlin 테스트명 한글 문장 규칙 +- **소유 Task:** `Task R7.2` / `P7-R2` + +**관찰 내용·근거** + +`P7-R1`에서 기존 영문 테스트명을 `home popular community adapter does not load original image for locked paid post`로 변경했다. 신규/변경 Kotlin 테스트명은 한글 문장을 사용해야 하며 외부 API명 등으로 영문을 유지할 경우에는 한글 주석이 필요하지만 두 조건 모두 충족하지 않는다. + +**재현 절차** + +1. `git diff -- HomeMainFragmentLayoutTest.kt`로 테스트명 변경을 확인한다. +2. `code-style.md` §5 테스트 관례와 대조한다. + +**영향·권장 조치** + +런타임 영향은 없으나 신규 테스트 규칙과 리뷰 완료 증거가 불일치한다. assertion과 production code는 유지하고 테스트명만 한글 문장으로 정정한다. + +**판정 기록** + +- 2026-07-31 — current diff와 코드 스타일 규칙 대조로 확정했다. +- 2026-07-31 — `P7-R2` 테스트명 정정과 focused/direct 영향 테스트, `ktlintCheck`, `git diff --check` 성공으로 수정 완료 판정했다. + +### REV-P7-003 — 원본 미로드 회귀 테스트가 adapter bind 동작을 검증하지 않는다 + +- **심각도:** Medium +- **상태:** 수정 완료 +- **관련 요구사항:** 유료 미구매 원본 미로드·기존 Coil 요청 해제·drawable 제거·lock overlay/가격 유지 +- **소유 Task:** `Task R7.3` / `P7-R3` + +**관찰 내용·근거** + +`P7-R3` 수정 전 `HomeMainFragmentLayoutTest.유료 미구매 인기 커뮤니티는 원본 이미지를 로드하지 않는다`는 `HomePopularCommunityAdapter.kt`를 문자열로 읽어 특정 코드 조각과 `BlurTransformation` 미존재만 확인했다. ViewHolder를 실제로 bind하지 않아 기존 image request가 해제되는지, 재활용된 view의 drawable가 비워지는지, lock overlay와 가격이 유지되는지를 증명하지 못했다. 특히 `P7-R1` 완료 증거가 이 동작들을 명시했고, 같은 보안 계약이 commit `2d58a876`에서 이미 한 차례 회귀했으므로 소스 문자열 검사만으로는 회귀 방지 강도가 부족했다. + +**영향·권장 조치** + +현재 production 코드에서는 잠금 유료 item의 URL이 loader로 전달되지 않고 blank 경로에서 `dispose()`와 `setImageDrawable(null)`을 호출하므로 즉시 production 결함은 확인되지 않았다. 다만 재활용된 image view 상태를 포함한 adapter bind 실행형 테스트로 보안 계약을 고정하고, 무료·구매 완료 경로가 기존대로 이미지를 로드하는지도 함께 검증한다. + +**판정 기록** + +- 2026-07-31 — production bind 코드와 현재 테스트 assertion, `P7-R1` 완료 증거를 대조해 실행형 회귀 검증 부재를 확정했다. +- 2026-07-31 — `P7-R3`에서 source 문자열 검사를 제거하고 실제 adapter/ViewHolder bind 회귀 테스트 2건으로 교체했다. 잠금 유료 원본 미로드·이전 요청 해제·drawable 제거·overlay/가격 유지와 무료·구매 완료 URL 전달을 검증했고, 보안 분기 mutation 실패와 원상 복구 후 성공을 확인해 수정 완료로 판정했다. + +## 7. 확정 항목의 plan·goal 전환 + +`Task R7.1` / `P7-R1`, `Task R7.2` / `P7-R2`, `Task R7.3` / `P7-R3`은 모두 수정 검증까지 완료했다. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | 코드·test·문서·git 이력 대조 완료 | +| 후보 항목 판정 완료 | 충족 | `REV-P7-001`, `REV-P7-002`, `REV-P7-003` 수정 완료 | +| 확정 항목 plan 반영 | 충족 | `Task R7.3` / `P7-R3` 실행·검증 완료 | +| 보류 항목의 담당·재개 조건 기록 | 해당 없음 | 보류 없음 | +| 검증 명령과 결과 기록 | 충족 | focused/full 검증 기록 연결 | + +**최종 결론:** 승인 + +**남은 항목:** 없음 + +## 9. 수정 후 검증 기록 + +- 2026-07-31 — `P7-R1` 실행으로 `HomePopularCommunityAdapter`의 유료 미구매 원본 이미지 로드 회귀를 복구했다. 변경 파일은 `HomePopularCommunityAdapter.kt`, `HomeMainFragmentLayoutTest.kt`, `prd.md`, `plan-task.md`, 이 리뷰 보고서다. RED는 `HomeMainFragmentLayoutTest.home popular community adapter does not load original image for locked paid post`를 추가한 뒤 focused test에서 `HomeMainFragmentLayoutTest.kt:1032` assertion 실패로 확인했다. GREEN은 유료 미구매 item의 `imageUrl`을 image loader에 전달하지 않고 `BlurTransformation` 경로를 제거하는 최소 수정 후 동일 focused test BUILD SUCCESSFUL로 확인했다. `FeedCommunityView.bind()` 호출은 유지하므로 lock overlay와 가격 표시는 기존 계약을 따른다. 후속 검증으로 `./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은 기존 경고다. +- 2026-07-31 — `P7-R2` 실행으로 `HomeMainFragmentLayoutTest`의 테스트명을 `유료 미구매 인기 커뮤니티는 원본 이미지를 로드하지 않는다`로 정정했다. Production code와 assertion은 변경하지 않았다. focused test, `HomeMainFragmentLayoutTest`·`V2ModalDialogTest` 직접 영향 회귀, `./gradlew :app:ktlintCheck`, `git diff --check`가 모두 성공했다. 남은 항목은 없다. +- 2026-07-31 — `P7-R3` 실행으로 `HomeMainFragmentLayoutTest`의 source 문자열 검사를 제거하고 `HomePopularCommunityAdapterTest.kt`를 추가했다. 실제 adapter/ViewHolder bind와 외부 이미지 loader 경계만 차단하는 테스트 2건으로 잠금 유료 원본 미로드·기존 Coil 요청 해제·drawable 제거·lock overlay/가격 유지, 무료·구매 완료 원본 URL 전달을 검증했다. 보안 분기를 일시적으로 직접 URL 전달로 바꾼 mutation 실행은 잠금 test 1건이 `HomePopularCommunityAdapterTest.kt:72`에서 실패했고, 원상 복구 후 전용 class 2건이 성공했다. 직접 영향 test·compile·ktlint가 성공했으며 전체 unit suite는 162 suites, 1,198 tests, 실패 0, error 0으로 성공했다. 최종 production 코드는 이번 Task에서 추가 변경하지 않았고 남은 항목은 없다. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase8-business-info-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase8-business-info-review.md new file mode 100644 index 00000000..08b7b5ef --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase8-business-info-review.md @@ -0,0 +1,57 @@ +# Phase 8 사업자 정보 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 8 / 사업자 정보 접기·더보기 | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 8 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: 3줄 접힘, inline 더보기·접기, 짧은 본문 처리와 레이아웃 안정성을 확인한다. +- 포함: `HomeBusinessInfoBinder`, 홈 Fragment/XML, 관련 Robolectric test. +- 제외: 사업자 정보 문구의 제품·법무 검토. + +## 3. 판정 기준 + +짧은 문구 no-action, 긴 문구 접힘/펼침 복귀, clickable span 범위와 별도 toggle View 미사용으로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeBusinessInfoBinder.kt`, `HomeMainFragment.setUpBusinessInfo()`, `fragment_v2_main_home.xml`. +- 테스트: `HomeMainFragmentLayoutTest`의 사업자 정보 inline action 계약. +- 검증: home/modal focused test·resource merge·compile·ktlint 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 초기 별도 toggle 계획은 승인된 후속 inline action 변경으로 대체됐고 현재 테스트가 해당 계약을 검증한다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | binder·XML·test 대조 완료 | +| 후보 항목 판정 완료 | 충족 | 확정 후보 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | focused 검증 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음. Phase 12의 사업자 정보 수동 항목은 사용자 확인으로 완료됐다. + +## 9. 수정 후 검증 기록 + +수정 항목 없음. diff --git a/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase9-state-routing-review.md b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase9-state-routing-review.md new file mode 100644 index 00000000..55125b9d --- /dev/null +++ b/docs/20260601_메인_홈_추천_UI와_API_연동/reviews/phase9-state-routing-review.md @@ -0,0 +1,57 @@ +# Phase 9 상태 바인딩·기본 라우팅 리뷰 + +## 1. 리뷰 정보 + +| 항목 | 내용 | +|---|---| +| 리뷰 대상 | Phase 9 / 상태 observe, schema, 라우팅·안정성 후속 Task | +| 기준 commit 또는 working tree | `a106548f` 기반 working tree | +| 리뷰 일자 | 2026-07-31 | +| 리뷰어 | Codex | +| 기준 문서 | `prd.md`, `plan-task.md` Phase 9 | +| 리뷰 상태 | 판정 완료 | + +## 2. 리뷰 목적과 범위 + +- 목적: sample 제거, 실제 ViewModel 상태 바인딩, loading/toast, 최신 schema, 이미지·배너 안정성 후속 조치를 확인한다. +- 포함: `HomeMainFragment`, recommendation data/model/state, `HomeBannerBinder`, image loader와 관련 tests. +- 제외: 목적지별 확정 라우팅은 Phase 10·11·13·16에서 별도 판정. + +## 3. 판정 기준 + +임시 데이터 미사용, 표시 상태 일관성, invalid data no-op, 중복 배너 식별, 캐시·대량 notify 회귀 방지로 판정했다. + +## 4. 검토한 근거 + +- 코드: `HomeMainFragment.kt`, `HomeRecommendationModels.kt`, `HomeRecommendationMappers.kt`, `HomeBannerBinder.kt`, `ImageLoaderProvider`. +- 테스트: `HomeMainFragmentLayoutTest`, `BannerViewTest`, `ImageLoaderProviderTest`. +- 검증: home/modal focused test·merge·compile·ktlint 성공, 최신 전체 1,198 tests 중 실패 0·error 0. + +## 5. 발견 사항 요약 + +확정 발견 사항 없음. + +## 6. 발견 사항 상세 + +없음. 계획에 누적된 schema·cache·banner 후속 수정이 현재 코드와 테스트에 남아 있으며 sample content 경로는 production에서 제거됐다. + +## 7. 확정 항목의 plan·goal 전환 + +전환 항목 없음. + +## 8. 리뷰 종료 판정 + +| 판정 항목 | 결과 | 근거 | +|---|---|---| +| 리뷰 범위 전체 확인 | 충족 | 상태·schema·안정성 코드 대조 완료 | +| 후보 항목 판정 완료 | 충족 | 확정 후보 없음 | +| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 | +| 검증 명령과 결과 기록 | 충족 | 자동 검증 성공 | + +**최종 결론:** 확정 발견 사항 없음 + +**남은 항목:** 없음. Phase 12 화면 수동 항목은 사용자 확인으로 완료됐다. + +## 9. 수정 후 검증 기록 + +수정 항목 없음.