# 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했으며 추가 확정 발견 사항은 없다.