# Phase 4 팔로잉 UI surface와 routing 리뷰 ## 1. 리뷰 정보 | 항목 | 내용 | |---|---| | 리뷰 대상 | Phase 4 / Task 4.1~4.9 | | 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree | | 리뷰 일자 | 2026-07-31 | | 리뷰어 | Codex | | 기준 문서 | 대상 `prd.md`, `plan-task.md`, 후속 pull-refresh/access 문서 | | 리뷰 상태 | 수정 검증 완료 | ## 2. 리뷰 목적과 범위 ### 목적 - 팔로잉 surface 전환, section visibility, adapter click 연결과 상태 observer가 요구사항을 충족하는지 확인한다. - 후속 pull refresh와 공통 액션 변경을 포함한 현재 실행 경로에서 회귀가 없는지 확인한다. ### 포함 범위 - `HomeMainFragment.kt`, `fragment_v2_main_home.xml` - 팔로잉 adapter 5개, `HomeFollowingFragmentSourceTest`, `HomeMainFragmentLoginGuardSourceTest` ### 제외 범위 - chevron 실제 목적지와 로그인 유도 UI 디자인. ## 3. 판정 기준 - item 상세 이동 누락은 확정 기능 요구사항 위반으로 High, 제한된 로그인 만료/refresh 조건의 복구 문제는 Medium으로 판정했다. ## 4. 검토한 근거 ### 문서와 코드 - PRD `On Air` item 터치 요구사항과 `이달의 스케줄` type/target 이동 요구사항 - `HomeMainFragment.kt:115-122`, `397-419`, `657-687` - `HomeFollowingLiveAdapter.kt:49-54`, `HomeFollowingScheduleAdapter.kt:53-67` ### 실행 환경 ```text OS: Darwin 25.0.0 x86_64 JDK: OpenJDK 17.0.15 Gradle wrapper: 8.14.3 연결 기기: 없음 ``` ### 실행한 검증 | 명령 | 결과 | 핵심 증거 | |---|---|---| | `./gradlew --no-daemon :app:testDebugUnitTest --tests "...HomeFollowingFragmentSourceTest" --tests "...HomeMainFragmentLoginGuardSourceTest"` | 성공 | source test 모두 PASS | | callback/observer 본문 수동 추적 | 문제 확인 | live/schedule callback은 `Unit`, LoginRequired는 refresh 종료 호출 없음 | | profile helper·Coil 2.7.0 source·저장소 `dispose()` 사용례 대조 | 문제 확인 | blank 분기가 drawable만 비우고 View 요청을 해제하지 않음 | | `HomeFollowingFragmentSourceTest` 이름과 테스트명 가이드 대조 | 문제 확인 | 완전한 영문 문장형 테스트명 11개 확인 | ## 5. 발견 사항 요약 | ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal | |---|---|---|---|---|---| | `REV-P4-001` | High | 수정 완료 | On Air와 스케줄 item 터치가 no-op | Task 4.6 | `P4-R1` 완료 | | `REV-P4-002` | Medium | 수정 완료 | LoginRequired 응답에서 pull refresh가 종료되지 않음 | Task 4.7 | `P4-R2` 완료 | | `REV-P4-003` | Medium | 수정 완료 | blank 프로필 이미지 바인딩이 이전 Coil 요청을 해제하지 않음 | Task 4.8 | `P4-R3` 완료 | | `REV-P4-004` | Low | 수정 완료 | 공용 source test의 영문 문장형 테스트명이 한글 테스트명 규칙과 불일치 | Task 4.9 | `P4-R4` 완료 | ## 6. 발견 사항 상세 ### REV-P4-001 — On Air와 스케줄 item 터치가 no-op - **심각도:** High - **상태:** 수정 완료 - **관련 요구사항:** PRD On Air `item 터치 시 해당 live room/detail`, 스케줄 `type/targetId 목적지` - **소유 Task:** Task 4.6 **관찰 내용** adapter click listener는 Fragment callback까지 전달되지만 `onFollowingLiveClick()`과 `onFollowingScheduleClick()` 본문이 `Unit`이라 아무 이동도 발생하지 않는다. **근거** - 코드: `HomeMainFragment.kt:119-122`, `669-671` - 테스트: source test는 callback 존재만 확인하고 실제 액션을 요구하지 않음 - 문서: Task 4.5 결과도 route 없는 skeleton임을 기록 **재현 또는 검증 절차** 1. On Air 또는 schedule item을 bind한다. 2. item root를 터치하면 adapter callback이 Fragment로 전달된다. 3. 실제 결과는 `Unit`; 요구 결과는 기존 공통 Live/Content/Community Action 이동이다. **영향** 팔로잉 핵심 섹션 두 곳에서 사용자가 상세 콘텐츠로 진입할 수 없다. **권장 조치** On Air `liveId`는 `LiveActionCoordinator`, schedule `type/targetId`는 기존 최근 활동 route와 공통 액션을 재사용한다. **판정 기록** - 2026-07-30 — PRD와 실제 callback 본문을 대조해 확정. - 2026-07-30 — Task 4.6에서 공통 Live/Content/Community Action 연결과 회귀 검증을 완료. ### REV-P4-002 — LoginRequired 응답에서 pull refresh가 종료되지 않음 - **심각도:** Medium - **상태:** 수정 완료 - **관련 요구사항:** 로그인 필요 상태 분기, 후속 홈 pull refresh 완료 정책 - **소유 Task:** Task 4.7 **관찰 내용** `Content`와 `Empty`는 `finishHomePullRefresh()`를 호출하지만 `LoginRequired`는 section만 숨긴다. 유효하지 않은 token 등으로 refresh API가 LoginRequired를 반환하면 SwipeRefresh spinner와 내부 refresh 추적 상태가 남는다. **근거** - 코드: `HomeMainFragment.kt:397-419` - 비교 경로: `Content` 406, `Empty` 411, `LoginRequired` 413 - 테스트: LoginRequired의 section 숨김만 검사하고 refresh 종료는 검사하지 않음 **재현 또는 검증 절차** 1. 팔로잉 탭에서 pull refresh를 시작한다. 2. API가 `isLoginRequired=true`를 반환한다. 3. 실제 결과는 `bindHomeFollowingEmpty(false)`만 실행된다. 4. 요구 결과는 section 숨김과 refresh 종료다. **영향** 로그인 만료·서버 세션 불일치 조건에서 refresh UI가 계속 동작 중인 것처럼 남는다. **권장 조치** LoginRequired 분기에서 팔로잉 refresh 종료를 보장하는 최소 분기와 회귀 테스트를 추가한다. **판정 기록** - 2026-07-30 — observer 분기와 refresh 상태 함수를 추적해 확정. - 2026-07-30 — Task 4.7에서 LoginRequired refresh 종료 분기와 회귀 검증을 완료. ### REV-P4-003 — blank 프로필 이미지 바인딩이 이전 Coil 요청을 해제하지 않음 - **심각도:** Medium - **상태:** 수정 완료 - **관련 요구사항:** PRD 팔로잉 크리에이터의 blank/실패 profile fallback, Phase 4 adapter image binding - **소유 Task:** Task 4.8 **관찰 내용** 팔로잉 creator/live/chat/schedule와 community profile이 재사용하는 `loadHomeCreatorProfileImage()`는 URL이 blank이면 drawable만 비운다. Coil 2.7.0은 View에 연결된 기존 요청을 별도로 유지하므로, 이전 item의 요청이 아직 진행 중이면 완료 시점에 재사용된 View에 이전 이미지가 다시 표시될 수 있다. **근거** - 코드: `HomeCreatorProfileImageLoader.kt:10-17`의 blank 분기에 `dispose()`가 없음 - 호출부: `HomeFollowingCreatorAdapter`, `HomeFollowingLiveAdapter`, `HomeFollowingChatAdapter`, `HomeFollowingScheduleAdapter`, `HomeFollowingNewsAdapter` - 의존성: `app/build.gradle`의 Coil `2.7.0`; 로컬 Coil source의 `ViewTargetRequestManager.dispose()`는 현재 요청을 취소·분리함 - 저장소 정상 사용례: `HomePopularCommunityAdapter.kt:67-70`은 blank image에서 `imageView.dispose()` 후 drawable을 비움 **재현 또는 검증 절차** 1. 재사용 가능한 profile `ImageView`에 완료 전 Coil 요청 A를 연결한다. 2. 같은 ViewHolder에 blank profile URL item B를 바인딩한다. 3. 현재 구현은 drawable만 비우고 요청 A를 유지한다. 4. 요청 A가 뒤늦게 완료되면 item B에 A의 프로필이 표시될 수 있다. 요구 결과는 blank 바인딩 시 요청 A를 해제하고 빈 상태를 유지하는 것이다. **영향** 빠른 스크롤이나 느린 이미지 응답 조건에서 creator/live/chat/schedule/news item에 다른 사용자의 프로필 이미지가 노출될 수 있다. **권장 조치** 공통 `loadHomeCreatorProfileImage()`의 blank 분기에서 Coil 요청을 해제한 뒤 drawable을 비운다. 각 adapter를 중복 수정하지 않고 공통 helper 한 곳을 수정하며, Robolectric에서 View 요청 연결 해제를 검증한다. **판정 기록** - 2026-07-30 — 공통 helper, 모든 팔로잉 호출부, Coil 2.7.0 요청 manager와 저장소 내 `dispose()` 사용례를 대조해 확정. - 2026-07-30 — 구현 전 Phase 4 Task 4.8 / `P4-R3`로 전환. - 2026-07-30 — Task 4.8에서 `loadHomeCreatorProfileImage()` blank 분기에 `dispose()`를 추가하고, Robolectric focused test와 팔로잉 회귀 검증을 완료. ### REV-P4-004 — 공용 source test의 영문 문장형 테스트명이 규칙과 불일치함 - **심각도:** Low - **상태:** 수정 완료 - **관련 요구사항:** `docs/agent-guides/build-test-style.md`, `docs/agent-guides/code-style.md`의 신규 Kotlin 테스트명 규칙 - **소유 Task:** 신규 Task 4.9 **관찰 내용** Phase 4에서 생성되고 후속 Phase가 함께 확장한 `HomeFollowingFragmentSourceTest`에 완전한 영문 문장형 backtick 테스트명 11개가 남아 있다. 테스트 작성 전부터 가이드가 신규 Kotlin 테스트명을 한글 문장으로 작성하도록 규정했지만 별도 한글 설명도 없다. **근거** - 테스트: `HomeFollowingFragmentSourceTest.kt`의 `home layout exposes...`부터 `following recent news adapter binds...`까지 영문 문장형 테스트명 11개 - 가이드: `docs/agent-guides/build-test-style.md`와 `docs/agent-guides/code-style.md`의 신규 테스트명 한글 규칙 - 이력: 테스트명 규칙은 `2026-05-28`, 대상 테스트는 `2026-06-25` 이후 추가됨 **재현 또는 검증 절차** 1. ``rg -n 'fun `[A-Za-z]' HomeFollowingFragmentSourceTest.kt``로 대상 이름을 확인한다. 2. 클래스의 완전한 영문 문장형 이름 11개와 가이드의 한글 문장 규칙을 대조한다. 3. 테스트 동작은 통과하지만 명명 규칙 불일치가 재현된다. **영향** runtime 동작에는 영향이 없지만 동일 기능 테스트의 언어 관례가 섞이고, 완료된 Phase가 저장소 테스트 규칙을 충족한다는 판정과 어긋난다. **권장 조치** assertion과 production 코드는 바꾸지 않고 해당 11개 테스트명만 검증 의도가 드러나는 한글 문장으로 변경한다. **판정 기록** - 2026-07-31 — 테스트 생성 이력, 현재 이름과 가이드를 대조해 확정하고 Phase 4 Task 4.9 / `P4-R4`로 전환. - 2026-07-31 — Task 4.9에서 대상 테스트명 11개를 한글 문장으로 변경하고 focused/팔로잉 회귀 검증을 완료. ## 7. 확정 항목의 plan·goal 전환 - Phase 4에 Task 4.6 / `P4-R1`, Task 4.7 / `P4-R2`를 추가해 완료했다. - 추가 재점검에서 Task 4.8 / `P4-R3`를 추가했으며 구현·검증을 완료했다. - 최종 재점검에서 Task 4.9 / `P4-R4`를 추가했으며 테스트명 정리를 완료했다. ## 8. 리뷰 종료 판정 | 판정 항목 | 결과 | 근거 | |---|---|---| | 리뷰 범위 전체 확인 | 충족 | UI→adapter→callback→공통 액션 경로 추적 | | 후보 항목 판정 완료 | 충족 | REV-P4-001~004 판정 완료 | | 확정 항목 plan 반영 | 충족 | Task 4.6~4.9 추가 | | 검증 명령과 결과 기록 | 충족 | focused/source test PASS와 수동 코드 추적 | **최종 결론:** 확정 발견 사항 모두 수정 완료 **남은 항목:** 없음 ## 9. 수정 후 검증 기록 - 2026-07-30: `REV-P4-001` 보완으로 팔로잉 On Air item은 `liveActionCoordinator.enterLiveRoom(item.liveId)`, 스케줄 item은 `CreatorActivityType`별 기존 Live/Content/Community Action을 호출하도록 연결했다. `targetId <= 0L`은 공통 action 호출 전 return하며 직접 `Intent` 또는 legacy extra 조립은 추가하지 않았다. - 2026-07-30: `REV-P4-002` 보완으로 `HomeFollowingUiState.LoginRequired` 분기에서 section 숨김 후 `finishHomePullRefresh(HOME_TAB_FOLLOWING)`을 호출하도록 변경했다. focused source test, 팔로잉 전체 회귀, `v2.live.action.*`, `v2.content.action.*`, `v2.community.action.*`, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check` PASS. - 2026-07-30: `REV-P4-003` 보완으로 `HomeCreatorProfileImageLoader.loadHomeCreatorProfileImage()`의 blank URL 분기에서 기존 Coil 요청을 `dispose()`로 해제한 뒤 drawable을 비우도록 변경했다. 구현 전 `HomeCreatorProfileImageLoaderTest` RED, 구현 후 focused GREEN, 팔로잉 전체 회귀와 신규 프로필 테스트, `:app:mergeDebugResources`, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check` PASS. - 2026-07-31 재점검: Fragment/source·공통 action·프로필 loader를 포함한 Phase 1~13 focused 회귀 113개 test, resource merge, Kotlin compile, ktlint PASS. 추가 확정 발견 사항 없음. - 2026-07-31 최종 재점검: `HomeFollowingFragmentSourceTest`의 영문 문장형 테스트명 11개를 `REV-P4-004`로 확정하고, production/test 동작을 변경하기 전에 Task 4.9 / `P4-R4`로 전환했다. - 2026-07-31 최종 검증: 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 자동 검증 성공과 별개로 이름 규칙 위반 `REV-P4-004`는 Task 4.9의 수정 항목으로 유지했다. - 2026-07-31: `REV-P4-004` 보완으로 `HomeFollowingFragmentSourceTest`의 영문 문장형 테스트명 11개를 한글 문장으로 변경했다. `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingNewsAdapterTest"`, 팔로잉 전체 회귀, `:app:compileDebugKotlin`, `:app:ktlintCheck` PASS. - 2026-07-31 이번 리뷰 재검증: surface 전환, section 상태, live/chat/schedule/news 공통 액션과 blank profile 요청 해제 경로를 다시 추적했다. 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint가 PASS해 `REV-P4-001~004` 수정 완료 판정을 유지한다.