fix(home): 팔로잉 탭 후속 검증을 반영한다

This commit is contained in:
2026-07-31 03:22:50 +09:00
parent f600d10e8b
commit 49f6fe592c
37 changed files with 2918 additions and 447 deletions

View File

@@ -0,0 +1,88 @@
# Phase 1 기존 구조와 작업 경계 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 1 / Task 1.1~1.3 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | `docs/20260625_메인_홈_팔로잉_탭/prd.md`, `docs/20260625_메인_홈_팔로잉_탭/plan-task.md` |
| 리뷰 상태 | 판정 완료 |
## 2. 리뷰 목적과 범위
### 목적
- 기존 홈 탭 삽입 지점, 재사용 후보, 제외 범위가 이후 구현과 일치하는지 확인한다.
- 레거시 직접 수정이나 추천·랭킹 리팩터링으로 범위가 확장되지 않았는지 확인한다.
### 포함 범위
- `HomeMainFragment`, `fragment_v2_main_home.xml`, v2 홈 widget/mapper와 Phase 1 문서 기록.
### 제외 범위
- Phase 2 이후 구현의 동작 상세와 Figma 실기기 대조.
## 3. 판정 기준
- 심각도와 상태는 `docs/sample/sample-review.md`의 Blocker/High/Medium/Low 및 확정/오탐/보류 기준을 적용했다.
- 완료 체크박스, 실제 코드 위치, 후속 Phase가 유지한 경계를 상호 대조했다.
## 4. 검토한 근거
### 문서와 코드
- `HomeMainFragment.kt:85-168`, `366-395`
- `fragment_v2_main_home.xml:9-34`, `227-393`
- `HomeCreatorProfileImageLoader`, `ChatRoomListItemResponse.toUiItems()`, `CreatorActivityType`
- Phase 1 Task 1.1~1.3과 PRD Non-Goals
### 실행 환경
```text
OS: Darwin 25.0.0 x86_64
JDK: OpenJDK 17.0.15
Gradle wrapper: 8.14.3
환경 변수: 민감정보 확인/출력 없음
```
### 실행한 검증
| 명령 또는 수동 검증 | 결과 | 핵심 증거 |
|---|---|---|
| `rg -n "HOME_TAB_FOLLOWING|showHomeTab|HomeCreatorProfileImageLoader|CreatorActivityType" ...` | 성공 | 홈 탭 분기와 재사용 경로 확인 |
| Phase 1 문서와 현재 파일 구조 대조 | 성공 | 신규 팔로잉 코드는 `v2.main.home` 하위, 레거시는 호출만 사용 |
## 5. 발견 사항 요약
확정 발견 사항 없음.
## 6. 발견 사항 상세
해당 없음. 이후 별도 작업에서 도입된 로그인 가드와 pull refresh는 각각의 PRD/계획으로 관리되고 있어 Phase 1 범위 위반으로 판정하지 않았다.
## 7. 확정 항목의 plan·goal 전환
전환 항목 없음.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | Task 1.1~1.3과 현재 구조 대조 완료 |
| 후보 항목 판정 완료 | 충족 | 범위 확장 후보를 후속 문서 근거로 오탐 판정 |
| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 |
| 검증 명령과 결과 기록 | 충족 | 검색·정적 대조 기록 |
**최종 결론:** 확정 발견 사항 없음
**남은 항목:** 없음
## 9. 수정 후 검증 기록
- 2026-07-31 재점검: Phase 1 경계와 현재 파일 구조를 다시 대조했고 기존 판정을 유지했다. Phase 1~13 focused 회귀 15개 suite/113개 test, resource merge, Kotlin compile, ktlint PASS.
- 2026-07-31 최종 검증: 현재 경계·파일 구조 판정을 유지했다. 통합 20개 suite·306개 test, resource merge, Kotlin compile, ktlint, Gradle task 목록, staged/unstaged diff 검사가 모두 PASS했다.
- 2026-07-31 이번 리뷰 재검증: Phase 1의 홈 탭 삽입 경계, v2 재사용 경로와 레거시 비수정 원칙을 현재 working tree에서 다시 대조했다. 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint, staged/unstaged diff 검사가 PASS해 기존 “확정 발견 사항 없음” 판정을 유지한다.

View File

@@ -0,0 +1,178 @@
# Phase 10 On Air 라이브 item 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 10 / Task 10.1~10.4 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md` |
| 리뷰 상태 | 수정 검증 완료 |
## 2. 리뷰 목적과 범위
### 목적
- 263dp × 100dp 캡슐 item의 필드·배경·profile binding과 가로 목록 배치를 확인한다.
### 포함 범위
- `item_home_following_live.xml`, `bg_home_following_live_capsule.xml`
- `HomeFollowingLiveAdapter.kt`, following RecyclerView와 source test
### 제외 범위
- item 터치 이동은 Phase 4 `REV-P4-001`에서 별도 판정.
## 3. 판정 기준
- 캡슐 내부 표현은 Task 10.1, 가로 item 간격은 PRD UX의 item spacing과 기존 홈 helper 관례로 판정했다.
## 4. 검토한 근거
- `item_home_following_live.xml:2-99`
- `bg_home_following_live_capsule.xml:2-8`
- `HomeFollowingLiveAdapter.kt:23-31`
- `HomeRecyclerItemLayoutParams.kt:7-14`
- creator/chat adapter의 동일 horizontal item layout parameter 사용
- `item_home_following_live.xml:5-6`: root `263dp x 100dp`
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| 팔로잉 전체 회귀 | 성공 | 39개 테스트 실패 0 |
| resource merge/compile/ktlint | 성공 | 모두 PASS |
| adapter layout parameter 수동 대조 | 문제 확인 | live adapter만 end margin helper 미사용 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P10-001` | Low | 수정 완료 | 연속된 On Air 캡슐 item 사이 간격이 없음 | Task 10.2 | `P10-R1` 완료 |
| `REV-P10-002` | Low | 수정 완료 | On Air margin을 source 문자열 테스트로 고정해 UI 표현 속성 테스트 금지 규칙을 위반함 | Task 10.3 | `P10-R2` 완료 |
| `REV-P10-003` | Medium | 수정 완료 | 간격 helper가 On Air root의 `263dp` 폭을 `WRAP_CONTENT`로 덮음 | Task 10.4 | `P10-R3` 완료 |
## 6. 발견 사항 상세
### REV-P10-001 — On Air 가로 item end margin 누락
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** PRD 가로 리스트 item 간격, Task 10.1 Figma 간격
- **소유 Task:** Task 10.2
**관찰 내용**
live adapter는 XML을 inflate한 뒤 layout parameter를 그대로 반환한다. RecyclerView에는 ItemDecoration이 없어 두 번째 item부터 캡슐이 바로 붙는다. creator/chat adapter는 공통 helper로 `spacing_12` end margin을 적용한다.
**근거**
- 코드: `HomeFollowingLiveAdapter.kt:23-31`
- 비교: `HomeFollowingCreatorAdapter.kt:28-30`, `HomeFollowingChatAdapter.kt:23-26`
- helper: `HomeRecyclerItemLayoutParams.kt:7-14`
**영향**
On Air item이 2개 이상일 때 Figma/기존 홈 가로 리스트와 다른 비핵심 UI가 노출된다.
**권장 조치**
신규 decoration 없이 기존 `recyclerItemLayoutParams(parent)`를 재사용한다.
**판정 기록**
- 2026-07-30 — RecyclerView/adapter/helper 경로 대조로 확정.
- 2026-07-30 — Task 10.2에서 기존 helper를 적용하고 resource/compile/회귀 검증을 완료.
### REV-P10-002 — On Air 간격 source test가 테스트 정책을 위반함
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** `docs/agent-guides/code-style.md` 테스트 관례
- **소유 Task:** Task 10.3
**관찰 내용**
Task 10.2에서 추가한 `following live adapter keeps horizontal item spacing``recyclerItemLayoutParams(parent)` 문자열을 검사해 item margin이라는 UI 표현 속성을 고정한다. 현재 가이드는 View 크기·margin·padding 같은 UI 표현 속성 테스트를 새로 작성하지 않도록 규정한다.
**근거**
- 테스트: `HomeFollowingFragmentSourceTest.kt:355-363`
- 가이드: `docs/agent-guides/code-style.md`의 “신규 테스트는 로직에 대한 테스트 코드만 작성” 및 UI margin 테스트 금지 규칙
- production: `HomeFollowingLiveAdapter.kt:23-30`의 helper 적용은 현재 유지되어야 함
**영향**
기능에는 영향이 없지만 금지된 테스트 유형이 회귀 Gate에 추가되고, 내부 구현 문자열 변경만으로 불필요한 실패가 발생한다.
**권장 조치**
해당 신규 source test만 제거하고 adapter의 helper 적용은 유지한다. 이 UI 간격은 `rg` 정적 대조, resource merge, compile, 수동 화면 확인으로 검증한다.
**판정 기록**
- 2026-07-30 — 신규 테스트 diff와 현재 코드 스타일 가이드를 대조해 확정.
- 2026-07-30 — Task 10.3에서 금지된 UI margin source test만 제거하고 helper 재사용 대체 검증을 완료.
### REV-P10-003 — 간격 보완이 On Air 고정 폭을 보존하지 않음
- **심각도:** Medium
- **상태:** 수정 완료
- **관련 요구사항:** Task 10.1 `263dp x 100dp`, Task 10.2 “기존 XML 크기 유지”
- **소유 Task:** Task 10.4
**관찰 내용**
Task 10.2는 `recyclerItemLayoutParams(parent)`를 적용해 XML 크기를 유지했다고 기록했지만, helper는 새 `RecyclerView.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)`를 반환한다. 따라서 inflate된 root의 `263dp x 100dp` 중 width 계약이 런타임에 `WRAP_CONTENT`로 교체된다.
**근거**
- layout: `item_home_following_live.xml:5-6`
- adapter: `HomeFollowingLiveAdapter.kt:23-30`
- helper: `HomeRecyclerItemLayoutParams.kt:7-14`
- 문서: Task 10.2 완료 증거와 검증 기록
**영향**
On Air 캡슐의 폭이 자식 content 측정값에 따라 축소되거나 달라져 제목/상태 영역과 연속 item 배치가 Figma와 불일치할 수 있다.
**권장 조치**
inflate된 기존 layout parameter의 width/height는 보존하고 `spacing_12` end margin만 추가한다. UI width/margin 자동 테스트는 다시 추가하지 않고 정적 대조·resource merge·compile·Task 5.3 수동 확인으로 검증한다.
**판정 기록**
- 2026-07-30 — XML root 크기와 adapter/helper의 최종 layout parameter를 대조해 확정.
- 2026-07-30 — 구현 전 Phase 10 Task 10.4 / `P10-R3`로 전환.
- 2026-07-30 — Task 10.4에서 inflate된 root의 기존 layout parameter를 보존하고 end margin만 적용하도록 수정했다.
## 7. 확정 항목의 plan·goal 전환
- Phase 10에 Task 10.2 / `P10-R1`을 추가해 완료했다.
- 재점검에서 Task 10.3 / `P10-R2`를 추가해 완료했다.
- 추가 재점검에서 Task 10.4 / `P10-R3`를 추가해 완료했다.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | layout/drawable/adapter 대조 |
| 후보 항목 판정 완료 | 충족 | REV-P10-001~003 판정 완료 |
| 확정 항목 plan 반영 | 충족 | Task 10.2, Task 10.3, Task 10.4 |
| 검증 명령과 결과 기록 | 충족 | 빌드 Gate PASS |
**최종 결론:** 확정 발견 사항 모두 수정 완료
**남은 항목:** 없음
## 9. 수정 후 검증 기록
- 2026-07-30: `HomeFollowingLiveAdapter.onCreateViewHolder()`에서 기존 horizontal list helper `recyclerItemLayoutParams(parent)`를 재사용해 XML 크기는 유지하고 `spacing_12` end margin을 적용했다. `HomeFollowingFragmentSourceTest`, 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint, `git diff --check` PASS.
- 2026-07-30: Task 10.3으로 `following live adapter keeps horizontal item spacing` source test만 제거하고 production helper 적용은 유지했다. `rg``HomeFollowingLiveAdapter``recyclerItemLayoutParams(parent)` 재사용을 대조했고 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint, `git diff --check` PASS.
- 2026-07-30: Task 10.4로 `HomeFollowingLiveAdapter``recyclerItemLayoutParams(parent)` 대신 inflate된 `RecyclerView.LayoutParams`를 보존하고 `spacing_12` end margin만 적용하도록 수정했다. focused test, 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint PASS. 연결 기기가 없어 실기기 확인은 Task 5.3 보류 상태를 유지했다.
- 2026-07-31 재점검: On Air binding/action을 포함한 Phase 1~13 focused 113개 test, resource merge, Kotlin compile, ktlint PASS. 연결 기기가 없어 실기기 폭/간격 확인은 Task 5.3에 유지했다.
- 2026-07-31 최종 검증: On Air binding/action 회귀를 포함한 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 추가 확정 수정 사항은 없고 실기기 확인은 Phase 5 Task 5.3에 유지한다.
- 2026-07-31 이번 리뷰 재검증: On Air 캡슐의 XML 고정 크기 보존, end margin과 live action 경로를 다시 추적했다. 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint가 PASS해 `REV-P10-001~003` 수정 완료 판정을 유지하고 시각 대조는 Phase 5 Task 5.3에 유지한다.

View File

@@ -0,0 +1,77 @@
# Phase 11 COMMUNITY_POST 상세 이동 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 11 / Task 11.1~11.3 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md`, 공통 community action 문서 |
| 리뷰 상태 | 판정 완료 |
## 2. 리뷰 목적과 범위
### 목적
- COMMUNITY_POST가 유효한 `postId`로 상세 이동하고 legacy extra 조립을 사용하지 않는지 확인한다.
- 후속 공통 도메인 액션 리팩터링이 Phase 11 의도를 유지하는지 확인한다.
### 포함 범위
- `HomeMainFragment.onFollowingNewsClick()`
- `CommunityActionCommand.PostDetail`, handler와 source test
### 제외 범위
- 상세 Activity 내부 데이터 로딩과 다른 news type 이동.
## 3. 판정 기준
- 유효 ID 검증, Login access, `CreatorChannelCommunityDetailActivity` intent 생성과 legacy 참조 부재를 기준으로 판정했다.
## 4. 검토한 근거
- `HomeMainFragment.kt:673-686`
- `CommunityAction.kt:5-20`
- `CommunityActionHandler.kt:13-42`
- `HomeMainFragmentLoginGuardSourceTest.kt:53-76`
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| `HomeMainFragmentLoginGuardSourceTest` | 성공 | source 계약 PASS |
| 팔로잉 전체 회귀/compile/ktlint/diff | 성공 | 모두 PASS |
## 5. 발견 사항 요약
확정 발견 사항 없음.
## 6. 발견 사항 상세
`postId <= 0L`은 Fragment와 공통 action에서 모두 무시되고, 유효 ID만 공통 Login access 후 `CreatorChannelCommunityDetailActivity`로 이동한다. Phase 11의 직접 intent 표현은 후속 공통 action 리팩터링으로 대체되었지만 사용자 동작과 보안 정책은 유지된다.
## 7. 확정 항목의 plan·goal 전환
전환 항목 없음.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | Fragment→Community Action→Activity 추적 |
| 후보 항목 판정 완료 | 충족 | 추가 후보 없음 |
| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 |
| 검증 명령과 결과 기록 | 충족 | focused/통합 PASS |
**최종 결론:** 확정 발견 사항 없음
**남은 항목:** 없음
## 9. 수정 후 검증 기록
- 2026-07-31 재점검: COMMUNITY_POST 공통 action과 ID guard를 다시 추적했고 Phase 1~13 focused 113개 test, resource merge, Kotlin compile, ktlint PASS. 추가 확정 발견 사항 없음.
- 2026-07-31 최종 검증: COMMUNITY_POST route 회귀를 포함한 통합 20개 suite·306개 test와 Kotlin compile, ktlint가 PASS했다. 추가 확정 발견 사항 없음.
- 2026-07-31 이번 리뷰 재검증: `COMMUNITY_POST`의 양수 `postId` 가드와 공통 Community Action 이동을 다시 추적했다. 통합 16개 suite·120개 test, Kotlin compile, ktlint가 PASS해 기존 “확정 발견 사항 없음” 판정을 유지한다.

View File

@@ -0,0 +1,111 @@
# Phase 12 전체 empty 상태 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 12 / Task 12.1~12.4 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md` |
| 리뷰 상태 | 수정 검증 완료 |
## 2. 리뷰 목적과 범위
### 목적
- Empty에서만 중앙 문구를 표시하고 Content/LoginRequired/Error에서는 숨기는지 확인한다.
- 한국어·영어·일본어 문구와 resource merge를 확인한다.
### 포함 범위
- `fragment_v2_main_home.xml`, `HomeMainFragment` empty/content binding, 3개 locale string, mapper Empty 조건과 source test.
### 제외 범위
- 로그인 유도 문구·CTA와 실기기 typography 시각 대조.
## 3. 판정 기준
- PRD의 전체 section empty 조건, fillViewport surface, locale별 문구와 visibility 분기를 기준으로 판정했다.
## 4. 검토한 근거
- `HomeFollowingUiState.kt:8-23`
- `HomeFollowingMappers.kt:18-35`
- `fragment_v2_main_home.xml:237-277`
- `HomeMainFragment.kt:397-419`, `503-524`
- `HomeFollowingFragmentSourceTest.kt:104-147`
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| mapper/source/팔로잉 전체 회귀 | 성공 | 관련 테스트 실패 0 |
| resource merge/compile/ktlint/diff | 성공 | 모두 PASS |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P12-001` | Low | 수정 완료 | empty source test가 금지된 크기·정렬·visibility 표현을 고정함 | Task 12.4 | `P12-R1` 완료 |
## 6. 발견 사항 상세
모든 표시 section이 비었을 때만 mapper가 `Empty`를 만들고, Fragment는 empty 문구를 표시하면서 adapter와 section을 비운다. Content/LoginRequired/Error의 숨김 경로와 세 locale 문구도 요구사항과 일치한다.
### REV-P12-001 — empty source test가 UI 표현 속성 정책을 위반함
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** `docs/agent-guides/code-style.md` 테스트 관례
- **소유 Task:** Task 12.4
**관찰 내용**
`following empty state shows centered reusable empty string only for all empty content` 테스트는 XML width/height/gravity/textAlignment/visibility와 Fragment의 직접 `View.VISIBLE/GONE` 문자열을 assertion한다. 이 테스트는 UI 크기·constraint·visibility 테스트 금지 규칙이 추가된 뒤 생성되었다.
**근거**
- 테스트: `HomeFollowingFragmentSourceTest.kt:107-150`
- 가이드: `docs/agent-guides/code-style.md:42-43`
- 이력: 가이드 규칙 `566f76df` 이후 테스트 `2c609836`, locale 문자열 assertion `dadf3a51` 추가
**영향**
mapper의 Empty 판정과 locale 문구라는 유효한 계약에 UI 배치 구현 문자열이 섞여 있어, 디자인 조정이 기능 회귀처럼 실패하고 현재 테스트 정책과 불일치한다.
**권장 조치**
mapper Empty 조건·locale 문구·상태 분기 검증은 유지하고 width/height/정렬/직접 visibility assertion만 제거한다. 중앙 배치는 정적 대조와 Task 5.3 실기기 확인으로 검증한다.
**판정 기록**
- 2026-07-30 — 테스트 추가 시점과 assertion 범위를 현행 가이드와 대조해 확정.
- 2026-07-30 — 구현 전 Phase 12 Task 12.4 / `P12-R1`로 전환.
- 2026-07-30 — Task 12.4에서 UI 표현 source assertion만 제거하고 locale 문구와 상태 분기 검증을 유지했다.
## 7. 확정 항목의 plan·goal 전환
- Phase 12에 Task 12.4 / `P12-R1`을 추가해 완료했다.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | mapper→layout→binding→resource 추적 |
| 후보 항목 판정 완료 | 충족 | REV-P12-001 확정 |
| 확정 항목 plan 반영 | 충족 | Task 12.4 |
| 검증 명령과 결과 기록 | 충족 | focused/통합 PASS |
**최종 결론:** 확정 발견 사항 모두 수정 완료
**남은 항목:** 실기기 중앙 정렬 대조는 Phase 5 Task 5.3에 포함
## 9. 수정 후 검증 기록
- 2026-07-30: Task 12.4로 empty source test명을 한글화하고 width/height/gravity/textAlignment/visibility 및 직접 `View.VISIBLE/GONE` 문자열 assertion을 제거했다. 3개 locale 문구와 `HomeFollowingUiState.Empty` 상태 분기 검증은 유지했다. focused test, 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint PASS.
- 2026-07-31 재점검: Empty mapper/state/locale 회귀를 포함한 Phase 1~13 focused 113개 test, resource merge, Kotlin compile, ktlint PASS. 연결 기기가 없어 중앙 정렬 실기기 확인은 Task 5.3에 유지했다.
- 2026-07-31 최종 검증: empty state·locale 회귀를 포함한 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 추가 확정 수정 사항은 없고 실기기 중앙 정렬 확인은 Phase 5 Task 5.3에 유지한다.
- 2026-07-31 이번 리뷰 재검증: 전체 section empty mapper 조건, 세 locale 문구와 상태별 표시 분기를 다시 대조했다. 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint가 PASS해 `REV-P12-001` 수정 완료 판정을 유지하고 중앙 정렬 시각 대조는 Phase 5 Task 5.3에 유지한다.

View File

@@ -0,0 +1,87 @@
# Phase 13 CREATOR_RANKING·AUDIO_CONTENT 이동 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 13 / Task 13.1~13.3 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md`, 공통 creator/content/community action |
| 리뷰 상태 | 판정 완료 |
## 2. 리뷰 목적과 범위
### 목적
- 미커밋 Phase 13 변경이 요청된 두 news type만 기존 공통 액션으로 이동시키는지 확인한다.
- 기존 COMMUNITY_POST와 명시적 no-op인 CONTENT_RANKING/PHOTO_CONTENT를 회귀시키지 않는지 확인한다.
### 포함 범위
- `HomeMainFragment.onFollowingNewsClick()`
- `HomeMainFragmentLoginGuardSourceTest`
- `CreatorAction`, `ContentAction`, `CommunityAction`의 ID/access 정책
- Phase 13 PRD/Task/검증 기록
### 제외 범위
- CONTENT_RANKING/PHOTO_CONTENT 신규 이동, API/DTO/mapper/UI 변경, 실제 기기 터치.
## 3. 판정 기준
- type 판별, payload target ID, 공통 액션 재사용, invalid ID 무시와 기존 COMMUNITY_POST 유지 여부를 기준으로 판정했다.
## 4. 검토한 근거
- working diff의 `HomeMainFragment.kt:673-687`
- `HomeMainFragmentLoginGuardSourceTest.kt:53-104`
- `CreatorAction.kt`, `ContentAction.kt`, `CommunityAction.kt`의 양수 ID 검증과 access 정책
- Phase 13 Task 13.1~13.3
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| `HomeMainFragmentLoginGuardSourceTest` | 성공 | 13개 테스트 실패 0 |
| 팔로잉 전체 회귀 | 성공 | 39개 테스트 실패 0 |
| resource merge/compile/ktlint/diff | 성공 | 모두 PASS |
| `adb devices` | 기기 없음 | 실제 터치 미실행 |
## 5. 발견 사항 요약
확정 발견 사항 없음.
## 6. 발견 사항 상세
- `CREATOR_RANKING``CreatorActionCommand.Profile(item.targetId)`로 전달된다.
- `AUDIO_CONTENT``ContentActionCommand.AudioDetail(item.contentId)`로 전달된다.
- 두 공통 action은 양수 ID와 access requirement를 자체 검증한다.
- COMMUNITY_POST의 `postId` 가드와 상세 이동은 유지된다.
- CONTENT_RANKING/PHOTO_CONTENT는 조건 불일치로 no-op을 유지한다.
실기기 터치는 연결 기기 부재로 실행하지 못했지만 Task 13.3이 이 경우 사유 기록을 완료 조건으로 허용하므로 보류 finding으로 전환하지 않았다.
## 7. 확정 항목의 plan·goal 전환
전환 항목 없음.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | working diff와 공통 액션 end-to-end 추적 |
| 후보 항목 판정 완료 | 충족 | 추가 후보 없음 |
| 확정 항목 plan 반영 | 해당 없음 | 확정 발견 사항 없음 |
| 검증 명령과 결과 기록 | 충족 | focused/통합/build PASS |
**최종 결론:** 확정 발견 사항 없음
**남은 항목:** 없음
## 9. 수정 후 검증 기록
- 2026-07-31 재점검: CREATOR_RANKING·AUDIO_CONTENT·COMMUNITY_POST 공통 action 경로를 다시 추적했고 Phase 1~13 focused 113개 test, resource merge, Kotlin compile, ktlint PASS. 추가 확정 발견 사항 없음.
- 2026-07-31 최종 검증: 최근 소식 상세 route 회귀를 포함한 통합 20개 suite·306개 test와 Kotlin compile, ktlint가 PASS했다. 추가 확정 발견 사항 없음.
- 2026-07-31 이번 리뷰 재검증: `CREATOR_RANKING`, `AUDIO_CONTENT`, 기존 `COMMUNITY_POST` 공통 액션과 `CONTENT_RANKING`·`PHOTO_CONTENT` no-op 범위를 다시 추적했다. 통합 16개 suite·120개 test, Kotlin compile, ktlint가 PASS해 기존 “확정 발견 사항 없음” 판정을 유지한다.

View File

@@ -0,0 +1,173 @@
# Phase 2 API·DTO·Repository·mapper 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 2 / Task 2.1~2.8 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md`, `docs/agent-guides/code-style.md` |
| 리뷰 상태 | 수정 검증 완료 |
## 2. 리뷰 목적과 범위
### 목적
- endpoint, optional auth header, DTO nested payload, mapper 상태 계약과 locale resource가 현재 PRD를 충족하는지 확인한다.
- Phase 9에서 교체된 최근 소식 계약을 Phase 2의 과거 기록과 혼동하지 않고 현재 계약 기준으로 판정한다.
### 포함 범위
- `HomeFollowingApi.kt`, `HomeFollowingModels.kt`, `HomeFollowingRepository.kt`
- `HomeFollowingAuthHeader.kt`, `HomeFollowingUiState.kt`, `HomeFollowingUiModels.kt`, `HomeFollowingMappers.kt`
- `HomeFollowingAuthHeaderTest`, `HomeFollowingMapperTest`, `HomeFollowingNewsAdapterTest`, 관련 string resource와 DI
### 제외 범위
- Fragment 표시·터치 이동과 서버 `permitAll` 실제 설정.
## 3. 판정 기준
- PRD endpoint/response contract, Gson/Kotlin 관례, mapper의 login/empty/content 분기와 `docs/sample/sample-review.md` 심각도 기준을 적용했다.
## 4. 검토한 근거
### 문서와 코드
- `HomeFollowingApi.kt:8-12`: query 없는 `GET /api/v2/home/following`, nullable header
- `HomeFollowingModels.kt:8-111`: DTO와 nested payload
- `HomeFollowingMappers.kt:18-158`: LoginRequired/Empty/Content, matching payload 필터, locale label
- `HomeFollowingNewsAdapter.kt:155-171`: UI model label을 읽지 않고 `FollowingNewsType.toFeedContentCategory()`로 표시 label을 결정
- `AppDI.kt`: API/Repository 등록
### 실행 환경
```text
OS: Darwin 25.0.0 x86_64
JDK: OpenJDK 17.0.15
Gradle wrapper: 8.14.3
```
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| `./gradlew --no-daemon :app:testDebugUnitTest --tests "...HomeFollowingAuthHeaderTest" --tests "...HomeFollowingMapperTest" --tests "...HomeFollowingViewModelTest"` | 성공 | 전체 20개 중 Phase 2 대상 13개(auth 3, mapper 10) 실패 0 |
| `./gradlew --no-daemon :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` | 성공 | resource/compile/style PASS |
| 2026-07-31 Phase 1~13 통합 focused 회귀 | 성공 | 15개 suite, 113개 test, failures/errors 0 |
| `git diff --check` | 성공 | whitespace 오류 없음 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P2-001` | Low | 수정 완료 | 후속 UI·오류 처리 변경 후 미사용 팔로잉 string resource 3개가 남음 | Task 2.7 | `P2-R1` 완료 |
| `REV-P2-002` | Low | 수정 완료 | Feed category 전환 후 미사용 `Content.labelResId`와 mapper가 남음 | Task 2.8 | `P2-R2` 완료 |
## 6. 발견 사항 상세
- Phase 2 기록의 `rank == null` 필터 문구는 Phase 9 nested payload 계약에서 `rank: Int`로 확정된 현재 계약에 의해 대체되었다. 현재 DTO·fixture·mapper가 동일 계약을 사용하므로 결함이 아닌 과거 기록으로 판정했다.
### REV-P2-001 — 후속 UI·오류 처리 변경 후 미사용 팔로잉 string resource가 남음
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** Task 2.4 string resource, Phase 6 팔로잉 크리에이터 header 제거, Task 3.2 공통 오류 Toast 패턴
- **소유 Task:** Task 2.7
**관찰 내용**
`screen_home_following_creators_title`, `screen_home_following_on_air_title`, `screen_home_following_error``values`, `values-en`, `values-ja`에 모두 선언되어 있지만 production/test/layout에서 참조되지 않는다. On Air 섹션과 상태는 `screen_home_following_on_air`를 사용하고, API 오류는 `common_error_unknown` Toast를 사용한다.
**근거**
- resource: `app/src/main/res/values/strings.xml:311-322`, `values-en/strings.xml:312-323`, `values-ja/strings.xml:312-323`
- code: `HomeMainFragment.kt:627-640`, `HomeFollowingViewModel.kt:73-75`
- 검색: 각 대상 key의 `R.string` / `@string` production/test 참조 0건
**재현 또는 검증 절차**
1. 3개 locale string file에서 위 key 선언을 확인한다.
2. `rg``app/src/main`, `app/src/test`의 resource 참조를 검색한다.
3. 실제 결과는 선언 외 참조 0건이다.
**영향**
런타임 오류는 없지만 번역·유지보수 대상을 불필요하게 늘리고, 후속 작업자가 현재 UI에 필요한 계약으로 오인할 수 있다.
**권장 조치**
3개 locale에서 위 3개 미사용 key만 제거하고, 사용 중인 `screen_home_following_on_air`와 나머지 팔로잉 문자열은 유지한다.
**판정 기록**
- 2026-07-30 — 3개 locale 선언과 production/test/layout 참조를 대조해 확정.
- 2026-07-30 — 코드는 수정하지 않고 Phase 2 Task 2.7 / `P2-R1`로 전환.
### REV-P2-002 — Feed category 전환 후 미사용 label UI model이 남음
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** Task 2.6 최근 소식 UI model/mapper, Task 6.4 Feed 위젯 재사용
- **소유 Task:** Task 2.8
**관찰 내용**
`HomeFollowingNewsUiItem.Content.labelResId`는 mapper가 값을 대입하고 test fixture가 검증하지만 production adapter는 이 필드를 읽지 않는다. 실제 label은 `content.type.toFeedContentCategory()``FeedContentCategory.labelResId`에서 결정된다.
**근거**
- UI model: `HomeFollowingUiModels.kt:74-86`
- mapper: `HomeFollowingMappers.kt:111-128`, `151-158`
- runtime: `HomeFollowingNewsAdapter.kt:155-171`, `FeedContentCategory.kt:6-15`
- 검색: 팔로잉 `Content.labelResId`의 production read 0건, test/fixture만 참조
**재현 또는 검증 절차**
1. `rg -n "labelResId|toLabelResId"` 결과에서 팔로잉 최근 소식 model·mapper·test 참조를 확인한다.
2. `HomeFollowingNewsAdapter.ContentViewHolder.bind()`의 category/label 결정 경로를 추적한다.
3. 실제 결과는 `labelResId`를 읽지 않고 `FeedContentCategory.Audio/Photo`를 사용한다.
**영향**
런타임 표시 오류는 없지만 동일 label 계약이 UI model mapper와 Feed category에 이중으로 남아 후속 문구·category 변경 시 test와 production이 엇갈릴 수 있다.
**권장 조치**
미사용 `Content.labelResId``FollowingNewsType.toLabelResId()`만 제거하고, 실제 표시 경로인 `FeedContentCategory.Audio/Photo`와 locale string 회귀를 유지한다.
**판정 기록**
- 2026-07-31 — production/test 참조와 Feed binding 경로를 대조해 확정.
- 2026-07-31 — production은 수정하지 않고 Phase 2 Task 2.8 / `P2-R2`로 전환.
- 2026-07-31 — Task 2.8에서 미사용 UI model 필드와 mapper를 제거하고 Feed category label 회귀 검증을 완료.
## 7. 확정 항목의 plan·goal 전환
- `plan-task.md` Phase 2에 Task 2.7 / `P2-R1`을 추가했다.
- objective: `[P2-R1]의 REV-P2-001을 최소 resource 삭제로 정리하고 팔로잉 resource 회귀를 방지한다.`
- `plan-task.md` Phase 2에 Task 2.8 / `P2-R2`를 추가했다.
- objective: `[P2-R2]의 REV-P2-002를 미사용 UI model/mapper 제거로 정리하고 Feed category label 회귀를 방지한다.`
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | API→Repository→mapper와 resource 확인 |
| 후보 항목 판정 완료 | 충족 | 과거 rank 문구는 오탐, `REV-P2-001~002` 판정 완료 |
| 확정 항목 plan 반영 | 충족 | Task 2.7 / `P2-R1`, Task 2.8 / `P2-R2` 추가 |
| 검증 명령과 결과 기록 | 충족 | focused test 및 빌드 품질 검증 PASS |
**최종 결론:** 확정 발견 사항 모두 수정 완료
**남은 항목:** 없음
## 9. 수정 후 검증 기록
- 2026-07-31: `REV-P2-001` 보완으로 `screen_home_following_creators_title`, `screen_home_following_on_air_title`, `screen_home_following_error`를 3개 locale에서 제거했다. `rg`로 production/test/layout 참조 0건을 확인했고, 사용 중인 `screen_home_following_on_air`, empty, ranking, section title 문자열은 유지했다. `./gradlew --no-daemon :app:mergeDebugResources :app:compileDebugKotlin`, `./gradlew --no-daemon --rerun-tasks :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingMapperTest"`, `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew --no-daemon :app:ktlintCheck`, `git diff --check` PASS.
- 2026-07-31 재점검: Phase 1~13 focused 회귀 15개 suite/113개 test, resource merge, Kotlin compile, ktlint는 PASS했다. `REV-P2-002`는 정적 사용처 대조로 확정했고, 이번 리뷰에서 production/test 코드는 수정하지 않았다.
- 2026-07-31: `REV-P2-002` 보완으로 `HomeFollowingNewsUiItem.Content.labelResId`, mapper 대입, `FollowingNewsType.toLabelResId()`를 제거했다. `PHOTO_CONTENT` 표시는 실제 `FeedContentCategory.Photo`와 locale string을 쓰는 adapter 회귀로 유지했다. mapper/news adapter focused test, 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint PASS.
- 2026-07-31 최종 검증: DTO·Repository·mapper 회귀를 포함한 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 추가 확정 발견 사항 없음.
- 2026-07-31 이번 리뷰 재검증: endpoint·optional auth·nested payload·mapper·Feed category label 경로를 다시 추적했다. 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint, staged/unstaged diff 검사가 PASS했으며 추가 확정 발견 사항은 없다.

View File

@@ -0,0 +1,123 @@
# Phase 3 ViewModel 상태와 API 호출 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 3 / Task 3.1~3.4 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md` |
| 리뷰 상태 | 수정 검증 완료 |
## 2. 리뷰 목적과 범위
### 목적
- Loading, success/login-required/error와 optional header 흐름이 구현·테스트에서 일치하는지 확인한다.
- 완료 기록이 주장하는 상태 발행 순서가 실제 assertion으로 보호되는지 확인한다.
### 포함 범위
- `HomeFollowingViewModel.kt`, DI 등록, `HomeFollowingViewModelTest.kt`
### 제외 범위
- Fragment rendering과 pull refresh UI.
## 3. 판정 기준
- 확정 상태 전환 요구사항 위반은 High, 기능은 맞지만 완료 증거가 누락된 검증 문제는 Low로 판정했다.
## 4. 검토한 근거
### 문서와 코드
- `HomeFollowingViewModel.kt:38-78`
- `HomeFollowingViewModelTest.kt:56-65`, `67-128`
- Task 3.1의 “loading 후 content 상태를 발행한다” 테스트 케이스와 Task 3.2 완료 기록
### 실행 환경
```text
OS: Darwin 25.0.0 x86_64
JDK: OpenJDK 17.0.15
Gradle wrapper: 8.14.3
```
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| `./gradlew --no-daemon :app:testDebugUnitTest --tests "...HomeFollowingViewModelTest"` | 성공 | 7개 테스트 실패 0 |
| 테스트 assertion 수동 대조 | 문제 확인 | 첫 테스트는 최종 Content와 `isLoading=false`만 확인하고 `Loading` 이력은 수집하지 않음 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P3-001` | Low | 수정 완료 | Loading → 결과 발행 순서가 테스트 이름과 완료 기록과 달리 검증되지 않음 | Task 3.4 | `P3-R1` 완료 |
## 6. 발견 사항 상세
### REV-P3-001 — Loading → 결과 발행 순서가 테스트로 보호되지 않음
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** Task 3.1 loading 후 content 상태 발행
- **소유 Task:** 신규 Task 3.4
**관찰 내용**
production은 `Loading`을 먼저 설정하지만 테스트는 `loadFollowing()` 완료 후 LiveData의 마지막 값만 읽는다. 테스트가 통과해도 `Loading` 발행이 제거되는 회귀를 잡지 못한다.
**근거**
- 코드: `HomeFollowingViewModel.kt:43-57`
- 테스트: `HomeFollowingViewModelTest.kt:56-65`
- 문서: Task 3.1, Task 3.2 검증 기록
**재현 또는 검증 절차**
1. `loadFollowing은 loading 후 content 상태를 발행한다` 테스트를 확인한다.
2. observer 이력 수집이나 `Loading` assertion이 없는지 대조한다.
3. 실제 결과는 최종 `Content``isLoading=false`만 검증된다.
**영향**
로딩 UI 계약이 회귀해도 Phase 3 focused test와 완료 기록은 계속 PASS할 수 있다.
**권장 조치**
observer가 수집한 상태 이력을 이용해 `Loading`, `Content` 순서를 한 테스트에서 검증한다. production 변경은 필요하지 않으며, 이미 존재하는 동작의 coverage 보강이므로 의도적인 RED를 만들지 않는다.
**판정 기록**
- 2026-07-30 — 테스트와 완료 기준을 직접 대조해 확정.
- 2026-07-30 — Task 3.4에서 observer 상태 이력 assertion을 추가하고 focused/전체 회귀 PASS로 수정 완료.
## 7. 확정 항목의 plan·goal 전환
- `plan-task.md` Phase 3에 Task 3.4 / `P3-R1`을 추가했다.
- objective: `[P3-R1]의 REV-P3-001을 테스트로 고정하고 Phase 3 회귀를 방지한다.`
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | ViewModel, DI, test 확인 |
| 후보 항목 판정 완료 | 충족 | REV-P3-001 확정 |
| 확정 항목 plan 반영 | 충족 | Task 3.4 추가 |
| 검증 명령과 결과 기록 | 충족 | focused test 7개 PASS |
**최종 결론:** 수정 검증 완료
**남은 항목:** 없음
## 9. 수정 후 검증 기록
- 2026-07-30: `HomeFollowingViewModelTest`에 observer 상태 이력 assertion을 추가해 `Loading → Content` 발행 순서를 검증했다. production 변경 없이 focused test와 팔로잉 전체 회귀, `git diff --check` PASS.
- 2026-07-31 재점검: ViewModel 7개 test와 Phase 1~13 focused 회귀 113개 test, resource merge, Kotlin compile, ktlint PASS. 추가 확정 발견 사항 없음.
- 2026-07-31 최종 검증: ViewModel 상태 전이를 포함한 통합 20개 suite·306개 test와 Kotlin compile, ktlint가 PASS했다. 추가 확정 발견 사항 없음.
- 2026-07-31 이번 리뷰 재검증: `Loading → Content`, optional auth, login-required, 초기 오류와 refresh fallback 상태를 다시 대조했다. 통합 16개 suite·120개 test와 Kotlin compile·ktlint가 PASS해 추가 확정 발견 사항이 없다.

View File

@@ -0,0 +1,247 @@
# 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` 수정 완료 판정을 유지한다.

View File

@@ -0,0 +1,107 @@
# Phase 5 통합 검증 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 5 / Task 5.1~5.3 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md`, `docs/agent-guides/build-test-style.md` |
| 리뷰 상태 | 판정 완료 / 수동 검증 보류 |
## 2. 리뷰 목적과 범위
### 목적
- Phase 5 자동 Gate를 현재 working tree에서 재실행하고 기록과 일치하는지 확인한다.
- Task 5.3의 실기기 수동 확인 가능 여부를 추정 없이 판정한다.
### 포함 범위
- 팔로잉 전체 unit/source test, resource merge, compile, ktlint, diff check, ADB 연결 확인.
### 제외 범위
- 테스트 계정·서버 데이터를 임의 생성하는 행위와 연결 기기 없는 상태의 수동 화면 성공 추정.
## 3. 판정 기준
- 자동 Gate 실패는 High 이상, 환경이 필요한 미실행 검증은 보류로 판정했다.
## 4. 검토한 근거
### 실행 환경
```text
OS: Darwin 25.0.0 x86_64
JDK: OpenJDK 17.0.15
Gradle wrapper: 8.14.3
ADB: 연결 기기 없음
```
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"` | 성공 | 39개 테스트, 실패 0 |
| `./gradlew --no-daemon :app:mergeDebugResources :app:compileDebugKotlin :app:ktlintCheck` | 성공 | 3개 task PASS |
| `git diff --check` | 성공 | 출력 없음 |
| `adb devices` | 성공 / 기기 없음 | `List of devices attached` 아래 항목 없음 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P5-001` | Low | 보류 | Figma/API 데이터 기반 실기기 수동 화면 검증 미완료 | 기존 Task 5.3 | 기기·계정 준비 후 재개 |
## 6. 발견 사항 상세
### REV-P5-001 — Figma/API 데이터 기반 실기기 수동 화면 검증 미완료
- **심각도:** Low
- **상태:** 보류
- **관련 요구사항:** Task 5.3
- **소유 Task:** 기존 Task 5.3
**관찰 내용**
자동 Gate는 모두 통과했으나 현재 ADB 연결 기기가 없어 실제 팔로잉 데이터 화면, scroll, 겹침, item 터치를 직접 확인하지 못했다.
**영향**
실제 렌더링과 상호작용 문제는 source test와 컴파일만으로 완전히 배제할 수 없다.
**권장 조치**
기기/에뮬레이터와 팔로잉 데이터가 있는 테스트 계정이 준비되면 기존 Task 5.3 체크리스트를 그대로 수행한다.
**판정 기록**
- 2026-07-30 — `adb devices` 결과 기기 없음으로 보류 유지.
- 2026-07-31 — Task 2.7 완료 후 `adb devices`를 재실행했으나 `List of devices attached` 아래 연결 기기가 없어 보류 유지.
## 7. 확정 항목의 plan·goal 전환
신규 전환 항목 없음. 기존 Task 5.3이 보류 범위와 재개 조건을 이미 소유한다.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | 자동 Gate와 ADB 확인 완료 |
| 후보 항목 판정 완료 | 충족 | REV-P5-001 보류 |
| 확정 항목 plan 반영 | 해당 없음 | 신규 확정 수정 없음 |
| 보류 항목의 담당·재개 조건 기록 | 충족 | Task 5.3, 기기·계정 준비 후 재개 |
| 검증 명령과 결과 기록 | 충족 | 39개 테스트 및 빌드 품질 PASS |
**최종 결론:** 외부 조건 대기
**남은 항목:** `REV-P5-001` / Task 5.3
## 9. 수정 후 검증 기록
- 2026-07-31 재점검: Phase 1~13 focused 회귀 15개 suite/113개 test, resource merge, Kotlin compile, ktlint PASS. `adb devices`는 정상 실행됐으나 연결 기기가 없어 `REV-P5-001` / Task 5.3 보류 판정을 유지했다.
- 2026-07-31 최종 검증: 통합 20개 suite·306개 test, resource merge, Kotlin compile, ktlint, Gradle task 목록, staged/unstaged diff 검사가 모두 PASS했다. `adb devices`에는 연결 기기가 없어 `REV-P5-001` / Task 5.3 보류 판정을 유지한다.
- 2026-07-31 이번 리뷰 재검증: 통합 16개 suite·120개 test(failures/errors/skipped 0), resource merge, Kotlin compile, ktlint, `./gradlew --no-daemon tasks --all`, staged/unstaged diff 검사가 PASS했다. `adb devices`에서 기기 `2cec640c34017ece` 연결은 확인했지만 테스트 계정·API 데이터 기반 팔로잉 화면 조작과 Figma 대조를 수행하지 않았으므로 `REV-P5-001` / Task 5.3은 보류 유지한다.

View File

@@ -0,0 +1,227 @@
# Phase 6 Figma 후속 수정 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 6 / Task 6.1~6.8 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md` |
| 리뷰 상태 | 수정 검증 완료 / 수동 검증 보류 |
## 2. 리뷰 목적과 범위
### 목적
- 크리에이터·최근 대화 UI 보정과 최근 소식 Feed 위젯 전환이 현재 코드에 유지되는지 확인한다.
- 전환 후 사용되지 않는 전용 resource가 남았는지 확인한다.
- 최근 대화 item의 XML 고정 폭이 adapter binding 이후에도 유지되는지 확인한다.
- creator·최근 대화 source test가 현행 UI 표현 속성 테스트 금지 규칙을 준수하는지 확인한다.
### 포함 범위
- `fragment_v2_main_home.xml`, creator/chat/news adapter와 layout, Feed 위젯 참조, source test.
### 제외 범위
- Phase 7 이후 Feed 내부 spacing과 Phase 9 DTO 계약.
## 3. 판정 기준
- runtime 기능에는 영향이 없지만 잘못된 preview와 obsolete resource를 남긴 문제는 Low로 판정했다.
## 4. 검토한 근거
### 문서와 코드
- `HomeFollowingNewsAdapter.kt:32-55`: Feed layout 3종 사용
- `fragment_v2_main_home.xml:359-368`: 과거 content layout preview 참조
- `item_home_following_news_rank.xml`, `item_home_following_news_content.xml`
- `HomeFollowingFragmentSourceTest.kt:185-218`
- `item_home_following_chat.xml:4`: root width `284dp`
- Task 6.7 수정 전 `HomeFollowingChatAdapter``HomeRecyclerItemLayoutParams.kt:7-14`: inflate된 width를 `WRAP_CONTENT`로 교체
- 현재 `HomeFollowingChatAdapter.kt:23-27`: inflate된 `RecyclerView.LayoutParams`의 width/height를 보존하고 end margin만 적용
- `HomeFollowingFragmentSourceTest.kt:292-360`: creator/chat XML 크기·orientation·style·maxLines·ellipsize 문자열 assertion
- `docs/agent-guides/code-style.md`: View 크기·margin·padding·constraint·visibility 등 UI 표현 속성 테스트 금지
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| `rg -n "item_home_following_news_(rank|content)" ...` | 성공 | runtime adapter 참조 없음, preview/doc/test만 존재 |
| `./gradlew --no-daemon :app:testDebugUnitTest --tests "...HomeFollowingFragmentSourceTest"` | 성공 | 16개 테스트 실패 0 |
| resource merge/compile/ktlint | 성공 | BUILD SUCCESSFUL |
| Task 6.7 수정 후 chat layout parameter 정적 대조 | 성공 | XML `284dp` width를 보존하고 end margin만 추가 |
| 2026-07-31 Phase 1~13 통합 focused 회귀 | 성공 | 15개 suite, 113개 test, failures/errors 0 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P6-001` | Low | 수정 완료 | Feed 전환 전 최근 소식 layout 2개와 preview 참조가 남음 | Task 6.5 | `P6-R1` 완료 |
| `REV-P6-002` | Low | 수정 완료 | 삭제된 최근 소식 layout이 상단 파일 구조에 생성 대상으로 남음 | Task 6.6 | `P6-R2` 완료 |
| `REV-P6-003` | Medium | 수정 완료 | 최근 대화 item의 XML `284dp` 폭이 런타임 `WRAP_CONTENT`로 덮임 | Task 6.7 | `P6-R3` 완료 |
| `REV-P6-004` | Low | 수정 완료 | creator·최근 대화 UI 표현 속성을 source 문자열로 검증함 | Task 6.8 | `P6-R4` 완료 |
## 6. 발견 사항 상세
### REV-P6-001 — Feed 전환 전 최근 소식 layout이 남음
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** Task 6.4 Feed 위젯 재사용
- **소유 Task:** Task 6.5
**관찰 내용**
runtime adapter는 `view_feed_rank/content/community`만 inflate하지만 과거 전용 news layout 두 개가 유지되고, RecyclerView preview는 그중 하나를 계속 가리킨다.
**근거**
- 코드: `HomeFollowingNewsAdapter.kt:38-55`
- resource: `fragment_v2_main_home.xml:368`
- 검색: `item_home_following_news_rank`는 runtime 참조 없음
**영향**
Layout Editor preview와 실제 UI가 다르고, 유지보수자가 사용 중인 resource로 오해할 수 있다.
**권장 조치**
preview를 `view_feed_content`로 바꾸고 obsolete layout 2개를 삭제한다.
**판정 기록**
- 2026-07-30 — 전체 resource 참조 검색으로 확정.
- 2026-07-30 — Task 6.5에서 preview 교체와 obsolete layout 삭제 후 resource/회귀 검증을 완료.
### REV-P6-002 — 삭제된 최근 소식 layout이 파일 구조에 남음
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** Task 6.5, 계획 문서 범위 변경 선반영 규칙
- **소유 Task:** Task 6.6
**관찰 내용**
Task 6.5와 현재 resource는 `item_home_following_news_rank.xml`, `item_home_following_news_content.xml`을 삭제했지만, `plan-task.md` 상단 `파일 구조`는 두 파일을 여전히 Create 대상으로 설명한다.
**근거**
- 문서: `plan-task.md` 상단 `파일 구조`와 Task 6.5
- 코드: `HomeFollowingNewsAdapter``view_feed_rank`, `view_feed_content`, `view_feed_community`를 사용
- resource: 두 obsolete layout은 현재 working tree에서 삭제 상태
**영향**
후속 작업자가 삭제된 resource를 현재 필수 파일로 오인하거나 다시 생성할 수 있다.
**권장 조치**
상단 파일 구조를 현재 Feed 위젯 재사용 구조에 맞추되 Task 6.5의 삭제 이력은 유지한다.
**판정 기록**
- 2026-07-30 — 현재 파일 목록, adapter 참조, 계획 문서 상단 목록을 대조해 확정.
- 2026-07-30 — Task 6.6에서 상단 파일 구조를 Feed layout 재사용/obsolete layout 삭제 이력 기준으로 갱신하고 대체 검증을 완료.
### REV-P6-003 — 최근 대화 고정 폭이 adapter에서 보존되지 않음
- **심각도:** Medium
- **상태:** 수정 완료
- **관련 요구사항:** Task 6.2의 Figma `284dp` box card, Task 6.3의 폭 보정 기록
- **소유 Task:** Task 6.7
**관찰 내용**
`item_home_following_chat.xml` root는 `284dp`지만 `HomeFollowingChatAdapter.onCreateViewHolder()`가 inflate 직후 `recyclerItemLayoutParams(parent)`로 전체 layout parameter를 교체한다. 해당 helper는 width와 height를 모두 `WRAP_CONTENT`로 새로 생성하므로 문서에 기록된 “XML 폭 유지”가 실제 코드에서는 성립하지 않는다.
**근거**
- layout: `item_home_following_chat.xml:4-5`
- adapter: `HomeFollowingChatAdapter.kt:23-26`
- helper: `HomeRecyclerItemLayoutParams.kt:7-14`
- 문서: Task 6.3의 최근 대화 item 폭 보정 검증 기록
**영향**
가로 목록에서 카드 폭이 Figma `284dp`가 아니라 자식 content의 wrap-content 측정값으로 결정되어 제목·메시지 영역과 item 간격이 설계와 다르게 보일 수 있다.
**권장 조치**
inflate된 root의 기존 width/height는 유지하고 end margin만 추가한다. 공통 helper 전체 리팩터링 없이 `HomeFollowingChatAdapter` 한 곳을 최소 수정하고, UI 크기 자동 테스트 대신 resource/compile/정적 대조와 Task 5.3 수동 확인을 사용한다.
**판정 기록**
- 2026-07-30 — XML root width, adapter 대입문, helper 반환값을 end-to-end로 대조해 확정.
- 2026-07-30 — 구현 전 Phase 6 Task 6.7 / `P6-R3`로 전환.
- 2026-07-30 — Task 6.7에서 inflate된 `RecyclerView.LayoutParams`의 width/height를 보존하고 end margin만 적용하도록 수정한 뒤 회귀 검증을 완료.
### REV-P6-004 — creator·최근 대화 UI 표현 속성을 source test로 고정함
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** `docs/agent-guides/code-style.md` 테스트 관례, Task 6.1~6.2
- **소유 Task:** Task 6.8
**관찰 내용**
`HomeFollowingFragmentSourceTest`가 creator item의 `75dp` 폭/높이와 typography style, 최근 대화 section의 XML orientation, `284dp`/`62dp` 폭/높이, `maxLines`/`ellipsize`를 파일 문자열로 검증한다. 이는 로직 테스트만 추가하고 UI 레이아웃/표현 속성 테스트를 작성하지 않는 현행 가이드와 맞지 않는다.
**근거**
- test: `HomeFollowingFragmentSourceTest.kt:292-302`, `331-361`
- guide: `docs/agent-guides/code-style.md` “신규 테스트는 로직에 대한 테스트 코드만 작성” 및 UI 표현 속성 검증 금지
- 비교: 동일 리뷰의 Task 7.7, 8.4, 10.3, 12.4는 같은 종류의 assertion을 이미 제거함
**재현 또는 검증 절차**
1. `rg -n 'layout_(width|height)|orientation|maxLines|ellipsize|Typography' HomeFollowingFragmentSourceTest.kt`를 실행한다.
2. Phase 6 creator/chat 테스트 범위에서 XML 표현 값 assertion을 확인한다.
3. production 동작은 정상이지만 테스트 정책 위반은 재현된다.
**영향**
런타임 결함은 아니지만 디자인 값 조정이 로직 회귀 실패로 잘못 표시되고, 가이드에서 정한 수동/Figma 대조 경계를 흐린다.
**권장 조치**
XML 크기·방향·style·말줄임 문자열 assertion만 제거하고, creator header 제거 구조, Direct badge 결정, 상대 시간 formatter, 필수 field 바인딩은 유지한다. production UI 값은 변경하지 않는다.
**판정 기록**
- 2026-07-31 — test source 검색과 현행 가이드를 대조해 확정.
- 2026-07-31 — production/test는 수정하지 않고 Phase 6 Task 6.8 / `P6-R4`로 전환.
- 2026-07-31 — Task 6.8에서 금지된 UI 표현 속성 source assertion만 제거하고 구조·바인딩·클릭 계약 회귀 검증을 완료.
## 7. 확정 항목의 plan·goal 전환
- Phase 6에 Task 6.5 / `P6-R1`을 추가해 완료했다.
- 재점검에서 Task 6.6 / `P6-R2`를 추가해 완료했다.
- 추가 재점검에서 Task 6.7 / `P6-R3`를 추가해 완료했다.
- 2026-07-31 재점검에서 Task 6.8 / `P6-R4`를 추가해 완료했다.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | UI resource와 adapter 참조 검색 |
| 후보 항목 판정 완료 | 충족 | REV-P6-001~004 판정 완료 |
| 확정 항목 plan 반영 | 충족 | Task 6.5~6.8 |
| 검증 명령과 결과 기록 | 충족 | source test 및 build PASS |
**최종 결론:** 확정 발견 사항 모두 수정 완료 / 실기기 검증은 외부 조건 대기
**남은 항목:** Phase 5 Task 5.3 실기기 확인
## 9. 수정 후 검증 기록
- 2026-07-30: 최근 소식 preview `tools:listitem`을 실제 runtime Feed layout인 `@layout/view_feed_content`로 교체하고 obsolete `item_home_following_news_rank.xml`, `item_home_following_news_content.xml`을 삭제했다. `rg` 기준 runtime 참조는 없고 문서/테스트 검증 참조만 남았다. `:app:mergeDebugResources`, `HomeFollowingFragmentSourceTest`, 팔로잉 전체 회귀, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check` PASS.
- 2026-07-30: Task 6.6으로 `plan-task.md` 상단 파일 구조를 현재 resource 구성에 맞춰 갱신했다. obsolete layout 2개는 삭제 이력으로만 남기고, 최근 소식 runtime layout은 `view_feed_rank`, `view_feed_content`, `view_feed_community` 재사용으로 명시했다. `rg` 대조와 `git diff --check` PASS.
- 2026-07-30: Task 6.7로 `HomeFollowingChatAdapter`가 새 `WRAP_CONTENT` layout parameter를 만들지 않고 inflate된 root의 `284dp` width와 height를 보존한 채 `spacing_12` end margin만 적용하도록 수정했다. focused test, 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint PASS. 연결 기기가 없어 실기기 확인은 Task 5.3 보류 상태를 유지했다.
- 2026-07-31 재점검: Phase 1~13 focused 회귀 15개 suite/113개 test, resource merge, Kotlin compile, ktlint는 PASS했다. `REV-P6-004`는 가이드 대조로 확정했고, 이번 리뷰에서 production/test 코드는 수정하지 않았다.
- 2026-07-31: `REV-P6-004` 보완으로 creator·최근 대화 source test에서 UI 표현 속성 문자열 assertion을 제거했다. creator header 제거, 필수 field id, Direct badge, 상대 시간 formatter, click listener 계약 검증은 유지했다. `rg` 대체 검증, `HomeFollowingFragmentSourceTest`, 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint PASS. 연결 기기가 없어 실기기 표현 확인은 Task 5.3 보류 상태를 유지했다.
- 2026-07-31 최종 검증: creator·최근 대화 회귀를 포함한 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 추가 확정 수정 사항은 없고 실기기 확인은 Phase 5 Task 5.3에 유지한다.
- 2026-07-31 이번 리뷰 재검증: creator/chat 고정 폭 보존, Feed 위젯 재사용과 obsolete layout 제거 상태를 다시 대조했다. 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint가 PASS해 `REV-P6-001~004` 수정 완료 판정을 유지하고, 시각 대조는 Phase 5 Task 5.3에 유지한다.

View File

@@ -0,0 +1,185 @@
# Phase 7 ranking 문장과 content feed 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 7 / Task 7.1~7.7 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md`, code style guide |
| 리뷰 상태 | 수정 검증 완료 |
## 2. 리뷰 목적과 범위
### 목적
- ranking message/highlight와 compact content Feed가 Figma·locale 요구를 충족하는지 확인한다.
- 과거 Task 7.3 ktlint 실패가 현재도 남았는지 재검증한다.
### 포함 범위
- `HomeFollowingNewsAdapter.kt`, `view_feed_content.xml`, `FeedContentView.kt`
- 한국어/영어/일본어 string resource, `FeedViewTest`, following source test
### 제외 범위
- Phase 9 nested payload와 Phase 13 routing.
## 3. 판정 기준
- 특정 locale에서 잘못된 사용자 문자열이 노출되는 문제는 Medium으로 판정했다.
## 4. 검토한 근거
### 문서와 코드
- `HomeFollowingNewsAdapter.kt:74-108`
- `values/strings.xml:318-319`, `values-en/strings.xml:319-320`, `values-ja/strings.xml:319-320`
- `view_feed_content.xml`, `FeedContentView.kt:30-112`
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| following source test + `FeedViewTest` | 성공 | 기존 layout/highlight source 계약 PASS |
| resource/compile/ktlint | 성공 | 현재 working tree에서 Task 7.3 명령 모두 PASS |
| locale message 수동 대입 | 문제 확인 | 영어·일본어 `%2$s``"7위"`가 전달됨 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P7-001` | Medium | 수정 완료 | 영어·일본어 ranking 문장에도 한국어 순위 단위 `"위"`가 노출됨 | Task 7.5 | `P7-R1` 완료 |
| `REV-P7-002` | Low | 수정 완료 | 신규 ranking 테스트명과 단일 실행 가이드가 저장소 규칙을 충족하지 않음 | Task 7.6 | `P7-R2` 완료 |
| `REV-P7-003` | Low | 수정 완료 | 가이드 제정 이후 content feed 크기·padding·margin source test를 추가함 | Task 7.7 | `P7-R3` 완료 |
## 6. 발견 사항 상세
### REV-P7-001 — ranking 순위 단위가 한국어로 고정됨
- **심각도:** Medium
- **상태:** 수정 완료
- **관련 요구사항:** 3개 locale string resource, ranking 문장형 message
- **소유 Task:** Task 7.5
**관찰 내용**
adapter가 locale과 무관하게 `val rankText = "${ranking.rank}위"`를 만든다. 영어와 일본어 문장은 resource로 번역되지만 placeholder에는 한국어 `위`가 그대로 들어간다.
**근거**
- 코드: `HomeFollowingNewsAdapter.kt:77-88`
- resource: 영어 `%1$s reached %2$s...`, 일본어 `ランキング%2$s...`
- 테스트: source 문자열 존재만 검사하고 locale별 결과 문자열은 검증하지 않음
**재현 또는 검증 절차**
1. locale을 English 또는 Japanese로 설정한다.
2. rank 7 item의 message를 생성한다.
3. 실제 결과 placeholder는 `7위`.
4. 요구 결과는 locale별 영어 표기 또는 일본어 `7位`다.
**영향**
영어·일본어 사용자에게 혼합 언어 문장이 노출되고 highlight 대상도 잘못된 단위를 포함한다.
**권장 조치**
rank format을 locale string resource로 분리하고 완성된 rank 문자열의 유효 index/highlight 범위를 테스트한다.
**판정 기록**
- 2026-07-30 — adapter와 세 locale resource를 대입해 확정.
- 2026-07-30 — Task 7.5에서 locale rank format과 highlight 단위 테스트를 추가하고 회귀 검증을 완료.
### REV-P7-002 — 신규 ranking 테스트와 실행 가이드 규칙 미준수
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** `docs/agent-guides/code-style.md` 테스트 관례, `docs/agent-guides/build-test-style.md` 단일 실행 예시 갱신 규칙
- **소유 Task:** Task 7.6
**관찰 내용**
새로 추가된 `HomeFollowingNewsAdapterTest`의 두 backtick 테스트명이 영문 문장이고, 새 테스트 클래스에 대응하는 단일 실행 예시가 `build-test-style.md`에 없다. Task 7.5 Files에는 가이드 수정이 명시되어 있지만 실제 변경에는 포함되지 않았다.
**근거**
- 테스트: `HomeFollowingNewsAdapterTest.kt:23`, `:30`
- 가이드: `code-style.md`의 신규 테스트명 한글 규칙과 테스트 추가 시 실행 예시 갱신 규칙
- 문서: Task 7.5 Files의 `docs/agent-guides/build-test-style.md`
**영향**
기능 동작에는 영향이 없지만 완료 체크리스트와 실제 변경이 불일치하고 신규 테스트 작성 관례가 깨진다.
**권장 조치**
신규 테스트명 2개만 한글화하고 `HomeFollowingNewsAdapterTest` 클래스 단위 실행 예시를 빌드 가이드에 추가한다. 기존 영어 테스트명 일괄 변경은 범위 밖으로 둔다.
**판정 기록**
- 2026-07-30 — 신규 테스트 diff와 현재 가이드 및 Task 7.5 Files를 대조해 확정.
- 2026-07-30 — Task 7.6에서 신규 테스트명 2개를 한글화하고 `HomeFollowingNewsAdapterTest` 실행 예시를 추가했다.
### REV-P7-003 — content feed UI 표현 속성을 source test로 고정함
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** `docs/agent-guides/code-style.md` 테스트 관례
- **소유 Task:** Task 7.7
**관찰 내용**
`FeedViewTest.content layout matches figma compact upload notice dimensions``view_feed_content.xml``padding`, `88dp` width/height, 두 margin 문자열을 직접 assertion한다. 이 테스트는 UI 크기·margin·padding 테스트 금지 규칙이 추가된 뒤 생성되었다.
**근거**
- 테스트: `FeedViewTest.kt:45-64`
- 가이드: `docs/agent-guides/code-style.md:42-43`
- 이력: 가이드 규칙 `566f76df` 이후 테스트 `61fe8a9f` 추가
**영향**
기능 로직이 아닌 Figma 표현값과 XML 구현 문자열이 회귀 Gate에 고정되어, 합법적인 UI 조정이나 구현 방식 변경만으로 테스트가 실패하고 현재 저장소 테스트 정책과 불일치한다.
**권장 조치**
해당 테스트만 제거하고 production layout/View는 유지한다. Figma 크기·간격은 정적 대조, resource merge, compile, Task 5.3 수동 화면 확인으로 검증한다.
**판정 기록**
- 2026-07-30 — test 추가 시점, assertion 내용, 현행 가이드를 대조해 확정.
- 2026-07-30 — 구현 전 Phase 7 Task 7.7 / `P7-R3`로 전환.
## 7. 확정 항목의 plan·goal 전환
- Phase 7에 Task 7.5 / `P7-R1`을 추가해 완료했다.
- 재점검에서 Task 7.6 / `P7-R2`를 추가해 완료했다.
- 추가 재점검에서 Task 7.7 / `P7-R3`를 추가해 완료했다.
- Task 7.3의 과거 ktlint 실패는 현재 재실행에서 PASS했으나 기존 기록과 체크박스는 유지했다.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | adapter, shared Feed, locale resource 확인 |
| 후보 항목 판정 완료 | 충족 | REV-P7-001~003 판정 완료 |
| 확정 항목 plan 반영 | 충족 | Task 7.5, Task 7.6, Task 7.7 |
| 검증 명령과 결과 기록 | 충족 | focused/통합 Gate PASS |
**최종 결론:** 확정 발견 사항 모두 수정 완료
**남은 항목:** 없음
## 9. 수정 후 검증 기록
- 2026-07-30: ranking 순위 단위를 `screen_home_following_ranking_rank_format` locale resource로 분리했다. 한국어는 `7위`, 영어는 `No. 7`, 일본어는 `7位`로 생성하며, 완성 문장에서 같은 문자열 범위를 `FeedRankHighlight`로 전달하도록 `HomeFollowingNewsAdapterTest`로 검증했다. focused test, Feed view test, 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint, `git diff --check` PASS.
- 2026-07-30 리뷰 게이트 후 보완: subject에 rank 문자열이 먼저 포함되는 경우를 추가 테스트로 고정하고, highlight 시작점을 `lastIndexOf(rankText)`로 변경해 실제 삽입 rank 문자열을 강조하도록 보완했다. RED/GREEN focused test와 최종 회귀 검증 PASS.
- 2026-07-30: Task 7.6으로 `HomeFollowingNewsAdapterTest`의 신규 ranking 테스트명 2개를 한글화하고 `docs/agent-guides/build-test-style.md`에 클래스 단위 실행 예시를 추가했다. focused test, ktlint, `git diff --check` PASS.
- 2026-07-30: Task 7.7로 `FeedViewTest`의 content feed 크기·padding·margin source test만 제거하고 production layout/View 코드는 유지했다. Feed focused test, 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint PASS.
- 2026-07-31 재점검: ranking locale/highlight와 Feed 회귀를 포함한 Phase 1~13 focused 113개 test, resource merge, Kotlin compile, ktlint PASS. 추가 확정 발견 사항 없음.
- 2026-07-31 최종 검증: ranking·공용 Feed 회귀를 포함한 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 추가 확정 발견 사항 없음.
- 2026-07-31 이번 리뷰 재검증: 세 locale ranking 문장·highlight와 공용 Feed 표시 경로를 다시 대조했다. 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint가 PASS해 추가 확정 발견 사항이 없다.

View File

@@ -0,0 +1,110 @@
# Phase 8 팔로잉 크리에이터 전체 버튼 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 8 / Task 8.1~8.4 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md` |
| 리뷰 상태 | 수정 검증 완료 |
## 2. 리뷰 목적과 범위
### 목적
- creator가 있을 때 마지막 `전체` item을 추가하고 기존 목록 화면으로 이동하는 계약을 확인한다.
### 포함 범위
- `HomeFollowingCreatorAdapter.kt`, `item_home_following_creator_all.xml`, `HomeMainFragment.openFollowingCreatorAll()`, source test.
### 제외 범위
- `FollowingCreatorActivity` 내부 동작과 creator API pagination.
## 3. 판정 기준
- item count/view type/layout token/공통 로그인 접근 경로를 대조했다.
## 4. 검토한 근거
- `HomeFollowingCreatorAdapter.kt:18-45`, `61-73`
- `item_home_following_creator_all.xml:2-28`
- `HomeMainFragment.kt:115-118`, `689-693`
- `HomeFollowingFragmentSourceTest.kt:304-334`
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| `HomeFollowingFragmentSourceTest` | 성공 | 16개 테스트 실패 0 |
| 팔로잉 전체 회귀 | 성공 | 39개 테스트 실패 0 |
| resource/compile/ktlint/diff | 성공 | 모두 PASS |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P8-001` | Low | 수정 완료 | `전체` item 테스트가 금지된 UI 표현 속성을 source 문자열로 검증함 | Task 8.4 | `P8-R1` 완료 |
## 6. 발견 사항 상세
creator가 비어 있으면 all item도 숨고, 1개 이상이면 마지막 view type으로 추가된다. layout width/높이 spacer/padding/color와 로그인 가드 경로도 요구사항과 일치한다.
### REV-P8-001 — `전체` item 테스트가 UI 표현 속성 정책을 위반함
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** `docs/agent-guides/code-style.md` 테스트 관례
- **소유 Task:** Task 8.4
**관찰 내용**
`following creators list appends all button as last item` 테스트는 adapter의 view type/item count/route 검증과 함께 XML의 width, height, padding, text color, visibility를 직접 assertion한다. 이 테스트는 UI 크기·padding·visibility 테스트 금지 규칙이 추가된 뒤 생성되었다.
**근거**
- 테스트: `HomeFollowingFragmentSourceTest.kt:311-341`
- 가이드: `docs/agent-guides/code-style.md:42-43`
- 이력: 가이드 규칙 `566f76df` 이후 테스트 `658b4f0a` 추가
**영향**
유효한 adapter 로직 검증에 금지된 UI 표현 문자열 검증이 섞여 있어, 단순 디자인/구현 변경도 기능 회귀처럼 실패하고 테스트 정책을 어긴다.
**권장 조치**
adapter view type·item count·route assertion은 유지하고 UI 표현 속성 assertion만 제거한다. 수정하는 테스트명은 한글화하고 XML 값은 정적 대조·resource merge·Task 5.3 수동 확인으로 검증한다.
**판정 기록**
- 2026-07-30 — 테스트 추가 시점과 assertion 범위를 현행 가이드와 대조해 확정.
- 2026-07-30 — 구현 전 Phase 8 Task 8.4 / `P8-R1`로 전환.
- 2026-07-30 — Task 8.4에서 UI 표현 source assertion만 제거하고 adapter item/route 검증을 유지했다.
## 7. 확정 항목의 plan·goal 전환
- Phase 8에 Task 8.4 / `P8-R1`을 추가해 완료했다.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | adapter→layout→route 추적 |
| 후보 항목 판정 완료 | 충족 | REV-P8-001 확정 |
| 확정 항목 plan 반영 | 충족 | Task 8.4 |
| 검증 명령과 결과 기록 | 충족 | focused/통합 PASS |
**최종 결론:** 확정 발견 사항 모두 수정 완료
**남은 항목:** 없음
## 9. 수정 후 검증 기록
- 2026-07-30: Task 8.4로 `전체` item 테스트명을 한글화하고 width/height/padding/visibility/color source assertion만 제거했다. adapter view type, item count, 마지막 item, `FollowingCreatorActivity` route 검증은 유지했다. focused test, 팔로잉 전체 회귀, resource merge, Kotlin compile, ktlint PASS.
- 2026-07-31 재점검: creator all item/route를 포함한 Phase 1~13 focused 113개 test, resource merge, Kotlin compile, ktlint PASS. 추가 확정 발견 사항 없음.
- 2026-07-31 최종 검증: creator 전체 item·route 회귀를 포함한 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 추가 확정 발견 사항 없음.
- 2026-07-31 이번 리뷰 재검증: creator가 있을 때 마지막 `전체` item 추가와 로그인 가드 이동 경로를 다시 추적했다. 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint가 PASS해 추가 확정 발견 사항이 없다.

View File

@@ -0,0 +1,168 @@
# Phase 9 최근 소식 nested payload 리뷰
## 1. 리뷰 정보
| 항목 | 내용 |
|---|---|
| 리뷰 대상 | Phase 9 / Task 9.1~9.7 |
| 기준 commit 또는 working tree | `f600d10e8b63b6fdc812b18a874b077dfe072ca8` + 2026-07-31 working tree |
| 리뷰 일자 | 2026-07-31 |
| 리뷰어 | Codex |
| 기준 문서 | 대상 `prd.md`, `plan-task.md` |
| 리뷰 상태 | 수정 검증 완료 |
## 2. 리뷰 목적과 범위
### 목적
- type별 nested payload DTO, mapper 필터, nullable image, adapter binding과 target ID 보존을 확인한다.
- Task 9.7의 미사용 `FeedAdapter`·`FeedImageViews` 제거가 실제 호출자 0건과 일치하는지 확인한다.
### 포함 범위
- `HomeFollowingModels.kt`, `HomeFollowingUiModels.kt`, `HomeFollowingMappers.kt`, `HomeFollowingNewsAdapter.kt`
- 삭제된 `v2/widget/feed/FeedAdapter.kt`와 현재 재사용 중인 `FeedItem`, `FeedRankView`, `FeedContentView`, `FeedCommunityView`
- mapper/source tests와 content ranking string resource
### 제외 범위
- 실제 서버 응답 샘플과 Phase 11/13 터치 목적지.
## 3. 판정 기준
- PRD의 nested payload 필드, matching payload null 제외, `contentImageUrl: String?`, `visibleFromAtUtc` 정책을 기준으로 판정했다.
## 4. 검토한 근거
- `HomeFollowingModels.kt:47-111`
- `HomeFollowingMappers.kt:64-149`
- `HomeFollowingUiModels.kt:55-103`
- `HomeFollowingNewsAdapter.kt:68-167`
- `HomeFollowingMapperTest.kt:88-182`
### 실행한 검증
| 명령 | 결과 | 핵심 증거 |
|---|---|---|
| mapper + Fragment source focused test | 성공 | mapper 10, source 16 테스트 실패 0 |
| 팔로잉 전체 회귀 | 성공 | 39개 테스트 실패 0 |
| resource/compile/ktlint/diff | 성공 | 모두 PASS |
| nullable image binding·Coil 2.7.0 source·저장소 `dispose()` 사용례 대조 | 문제 확인 | blank 분기가 drawable만 비우고 View 요청을 해제하지 않음 |
| DTO→mapper→Feed item의 ID 의미 대조 | 문제 확인 | content/post ID가 creator ID 필드에 대입됨 |
| `rg -n "FeedAdapter|FeedImageViews" app/src/main/java app/src/test/java docs/20260625_메인_홈_팔로잉_탭 docs/agent-guides` | 성공 | production/test 호출자 0건, Task 9.7 이력만 남음 |
## 5. 발견 사항 요약
| ID | 심각도 | 상태 | 제목 | 소유 Task | 후속 goal |
|---|---|---|---|---|---|
| `REV-P9-001` | Medium | 수정 완료 | nullable 최근 소식 이미지 바인딩이 이전 Coil 요청을 해제하지 않음 | Task 9.5 | `P9-R1` 완료 |
| `REV-P9-002` | Low | 수정 완료 | API에 없는 creator ID를 content/post ID로 합성해 Feed model에 전달함 | Task 9.6 | `P9-R2` 완료 |
## 6. 발견 사항 상세
matching payload가 null이면 `mapNotNull`로 제외되고, content image null은 UI model까지 보존된다. `visibleFromAtUtc` 기준 상대 시간과 payload별 target ID도 현재 계약과 일치한다. Task 9.7에서 삭제한 `FeedAdapter``FeedImageViews`는 production/test 호출자가 없고, 최근 소식은 현재 `FeedItem`과 타입별 Feed view를 직접 재사용하므로 삭제에 따른 확정 발견 사항은 없다.
### REV-P9-001 — nullable 최근 소식 이미지 바인딩이 이전 Coil 요청을 해제하지 않음
- **심각도:** Medium
- **상태:** 수정 완료
- **관련 요구사항:** PRD `contentImageUrl`·`imageUrl` nullable 계약, Task 9.3 Feed image fallback
- **소유 Task:** Task 9.5
**관찰 내용**
`HomeFollowingNewsAdapter.bindImage()`는 blank URL에서 `setImageDrawable(null)`만 호출한다. 같은 ViewHolder가 이미지가 있는 item에서 nullable image item으로 재사용될 때 이전 Coil 요청이 진행 중이면, 그 요청이 완료되면서 현재 item에 이전 이미지가 표시될 수 있다.
**근거**
- 코드: `HomeFollowingNewsAdapter.kt:187-203`의 blank 분기에 `dispose()`가 없음
- 계약: `FollowingContentNewsResponse.contentImageUrl`, `FollowingContentRankingNewsResponse.contentImageUrl`, `FollowingCommunityPostNewsResponse.imageUrl`은 nullable
- 테스트: `HomeFollowingMapperTest`는 null 보존만 확인하고 adapter 재사용 시 이미지 요청 상태는 검증하지 않음
- 저장소 정상 사용례: `HomePopularCommunityAdapter.kt:67-70`은 blank image에서 `imageView.dispose()`와 drawable 초기화를 함께 수행함
- Coil 2.7.0 로컬 source: `ImageView.dispose()`는 View의 현재 요청을 취소하고 분리함
**재현 또는 검증 절차**
1. 최근 소식 ViewHolder의 image view에 item A의 완료 전 Coil 요청을 연결한다.
2. 같은 holder에 `contentImageUrl = null` 또는 `imageUrl = null`인 item B를 바인딩한다.
3. 현재 blank 분기는 drawable만 비우므로 요청 A가 남는다.
4. 요청 A가 완료되면 B에 A의 이미지가 표시될 수 있다. 요구 결과는 blank 바인딩 시 요청을 해제하고 빈 이미지 상태를 유지하는 것이다.
**영향**
빠른 스크롤이나 느린 이미지 응답 조건에서 ranking/content/community 최근 소식 카드에 다른 item의 이미지가 노출될 수 있다.
**권장 조치**
공유되는 `bindImage()` blank 분기에서 `imageView.dispose()` 후 drawable을 비운다. 한 대표 nullable content item의 ViewHolder 재사용을 Robolectric test로 고정하고 mapper/Feed 회귀를 실행한다.
**판정 기록**
- 2026-07-30 — nullable DTO·adapter binding·Coil request lifecycle·저장소 내 정상 해제 패턴을 대조해 확정.
- 2026-07-30 — 구현 전 Phase 9 Task 9.5 / `P9-R1`로 전환.
- 2026-07-30 — Task 9.5에서 `HomeFollowingNewsAdapter.bindImage()` blank 분기에 `dispose()`를 추가하고, Robolectric adapter binding test와 팔로잉 회귀 검증을 완료.
### REV-P9-002 — API에 없는 creator ID를 다른 target ID로 합성함
- **심각도:** Low
- **상태:** 수정 완료
- **관련 요구사항:** Task 6.4의 API 미제공 값 비합성 정책, Phase 9 nested payload 계약
- **소유 Task:** 신규 Task 9.6
**관찰 내용**
`FollowingContentNewsResponse``FollowingCommunityPostNewsResponse`에는 `creatorId`가 없지만 adapter가 `FeedItem.Content.creatorId`에는 `content.targetId`, `FeedItem.Community.creatorId`에는 `community.targetId`를 문자열로 넣는다. 두 target은 각각 `contentId`, `postId`이므로 creator ID가 아니다.
**근거**
- DTO: `HomeFollowingModels.kt`의 content/community nested payload에는 `creatorId` 없음
- mapper: content `targetId = contentId`, community `targetId = postId`
- adapter: `HomeFollowingNewsAdapter.kt`에서 두 `FeedItem.creatorId`에 각각 위 target ID를 대입
- 현재 클릭 경로: adapter callback은 원본 `HomeFollowingNewsUiItem`을 캡처하므로 현행 상세 이동에는 잘못된 `creatorId`가 사용되지 않음
**재현 또는 검증 절차**
1. content/community nested payload와 UI model의 target ID 의미를 대조한다.
2. 두 Feed item binding의 `creatorId` 대입값을 확인한다.
3. 실제 결과는 content/post ID가 creator ID 필드에 들어가며, 요구 결과는 서버가 제공하지 않은 ID를 합성하지 않는 것이다.
**영향**
현재 화면 클릭에는 직접 영향이 없지만 Feed model의 의미가 잘못되고, 공통 Feed 위젯이 추후 creator ID를 사용하면 잘못된 크리에이터 이동이나 분석 값으로 이어질 수 있다.
**권장 조치**
서버 계약을 추정해 확장하지 말고 두 Feed item의 `creatorId`를 빈 값으로 전달한다. 실제 `contentId`, `postId`와 기존 원본 UI model click callback은 유지한다.
**판정 기록**
- 2026-07-31 — DTO→mapper→adapter→click callback을 추적해 확정하고 Phase 9 Task 9.6 / `P9-R2`로 전환.
- 2026-07-31 — Task 9.6에서 `FeedItem.Content``FeedItem.Community``creatorId`를 빈 값으로 전달하도록 수정하고 focused/팔로잉 회귀 검증을 완료.
## 7. 확정 항목의 plan·goal 전환
- Phase 9에 Task 9.5 / `P9-R1`을 추가했으며 구현·검증을 완료했다.
- 최종 재점검에서 Task 9.6 / `P9-R2`를 추가했으며 creator ID 합성 제거를 완료했다.
- Task 9.7 / `P9-R3`의 미사용 `FeedAdapter` 제거와 문서 정리를 현재 호출자 검색·컴파일·회귀 결과로 확인했다.
## 8. 리뷰 종료 판정
| 판정 항목 | 결과 | 근거 |
|---|---|---|
| 리뷰 범위 전체 확인 | 충족 | DTO→mapper→adapter 추적과 Task 9.7 미사용 호출자 검색 |
| 후보 항목 판정 완료 | 충족 | REV-P9-001~002 판정 완료 |
| 확정 항목 plan 반영 | 충족 | Task 9.5, Task 9.6 추가 |
| 검증 명령과 결과 기록 | 충족 | focused/통합 PASS |
**최종 결론:** 확정 발견 사항 모두 수정 완료
**남은 항목:** 없음
## 9. 수정 후 검증 기록
- 2026-07-30: `REV-P9-001` 보완으로 `HomeFollowingNewsAdapter.bindImage()`의 blank image 분기에서 기존 Coil 요청을 `dispose()`로 해제한 뒤 drawable을 비우도록 변경했다. 구현 전 `HomeFollowingNewsAdapterTest` RED, 구현 후 focused GREEN, 팔로잉 전체 회귀와 신규 프로필 테스트, `:app:mergeDebugResources`, `:app:compileDebugKotlin`, `:app:ktlintCheck`, `git diff --check` PASS.
- 2026-07-31 재점검: nested payload mapper·nullable image 회귀를 포함한 Phase 1~13 focused 113개 test, resource merge, Kotlin compile, ktlint PASS. 추가 확정 발견 사항 없음.
- 2026-07-31 최종 재점검: API에 없는 creator ID를 content/post ID로 대신 채우는 binding을 `REV-P9-002`로 확정하고, production/test 코드를 변경하기 전에 Task 9.6 / `P9-R2`로 전환했다.
- 2026-07-31 최종 검증: nested payload·adapter 회귀를 포함한 통합 20개 suite·306개 test와 resource merge, Kotlin compile, ktlint가 PASS했다. 자동 검증 성공과 별개로 ID 의미 계약 불일치 `REV-P9-002`는 Task 9.6의 수정 항목으로 유지했다.
- 2026-07-31: `REV-P9-002` 보완으로 `HomeFollowingNewsAdapter`의 content/community Feed model에서 API가 제공하지 않는 `creatorId`를 content/post ID로 합성하지 않고 빈 값으로 전달하도록 변경했다. RED focused test 실패를 확인한 뒤 GREEN으로 전환했고, focused test, 팔로잉 전체 회귀, `:app:compileDebugKotlin`, `:app:ktlintCheck` PASS.
- 2026-07-31 이번 리뷰 재검증: nested payload·nullable image·ID 의미와 Task 9.7의 미사용 `FeedAdapter` 삭제 상태를 다시 대조했다. production/test `FeedAdapter|FeedImageViews` 호출자는 0건이며, 통합 16개 suite·120개 test, resource merge, Kotlin compile, ktlint가 PASS해 추가 확정 발견 사항이 없다.