docs(home): 팔로잉 최근 소식 구현 검증을 기록한다

This commit is contained in:
2026-06-30 23:54:21 +09:00
parent 3f3bacebd9
commit f74f5a534d

View File

@@ -20,7 +20,7 @@
- 로그인 유도 화면의 실제 UI, 문구, CTA, 로그인 완료 후 복귀 정책은 별도 확정 후 구현한다. - 로그인 유도 화면의 실제 UI, 문구, CTA, 로그인 완료 후 복귀 정책은 별도 확정 후 구현한다.
- `recentNews` 시간은 `visibleFromAtUtc`를 디바이스 타임존 기준으로 상대 시간 표시한다. - `recentNews` 시간은 `visibleFromAtUtc`를 디바이스 타임존 기준으로 상대 시간 표시한다.
- `PHOTO_CONTENT` label은 우선 `화보`로 표시한다. - `PHOTO_CONTENT` label은 우선 `화보`로 표시한다.
- ranking news의 `rank`가 null이면 해당 news item 표시하지 않는다. - `recentNews``type`에 대응하는 nested payload가 null이면 해당 news item 표시하지 않는다.
- `monthlySchedules`는 서버가 이번 달 범위로 정렬해서 내려주며 앱은 재정렬하지 않는다. - `monthlySchedules`는 서버가 이번 달 범위로 정렬해서 내려주며 앱은 재정렬하지 않는다.
- 섹션 title chevron은 터치 콜백까지만 연결하고 실제 이동 목적지는 만들지 않는다. - 섹션 title chevron은 터치 콜백까지만 연결하고 실제 이동 목적지는 만들지 않는다.
- 구현 완료 후 최소 다음 명령을 실행한다. - 구현 완료 후 최소 다음 명령을 실행한다.
@@ -43,14 +43,16 @@
- 팔로잉 크리에이터, On Air, 최근 대화, 이달의 스케줄, 최근 소식 섹션 배치와 spacing은 Figma `24:5682`를 기준으로 확인한다. - 팔로잉 크리에이터, On Air, 최근 대화, 이달의 스케줄, 최근 소식 섹션 배치와 spacing은 Figma `24:5682`를 기준으로 확인한다.
- Phase 5: 필수 참조 - Phase 5: 필수 참조
- 최종 수동 화면 검증은 PRD의 포함/제외 항목과 실제 화면을 대조한다. - 최종 수동 화면 검증은 PRD의 포함/제외 항목과 실제 화면을 대조한다.
- Phase 9: Figma 참조 불필요
- 최근 소식 Response 계약 변경은 DTO/mapper/test fixture 중심으로 진행하고, 기존 Feed 위젯 표시 정책과 레이아웃은 유지한다.
--- ---
## 파일 구조 ## 파일 구조
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingApi.kt` - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingApi.kt`
- `GET /api/v2/home/following` Retrofit endpoint를 정의한다. - `GET /api/v2/home/following` Retrofit endpoint를 정의한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingModels.kt` - Create/Modify: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingModels.kt`
- `HomeFollowingTabResponse`, `FollowingCreatorResponse`, `FollowingLiveResponse`, `FollowingScheduleResponse`, `FollowingNewsResponse`, `FollowingNewsType` DTO를 정의한다. - `HomeFollowingTabResponse`, `FollowingCreatorResponse`, `FollowingLiveResponse`, `FollowingScheduleResponse`, `FollowingNewsResponse`, `FollowingCreatorRankingNewsResponse`, `FollowingContentNewsResponse`, `FollowingContentRankingNewsResponse`, `FollowingCommunityPostNewsResponse`, `FollowingNewsType` DTO를 정의한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingRepository.kt` - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingRepository.kt`
- API 호출을 repository method로 감싼다. - API 호출을 repository method로 감싼다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingUiState.kt` - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingUiState.kt`
@@ -58,7 +60,7 @@
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingUiModels.kt` - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingUiModels.kt`
- 팔로잉 크리에이터, live, chat, schedule, news section/item UI model을 정의한다. - 팔로잉 크리에이터, live, chat, schedule, news section/item UI model을 정의한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingMappers.kt` - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingMappers.kt`
- DTO를 UI model/state로 변환하고, `rank == null` news 숨김, `PHOTO_CONTENT` label, `visibleFromAtUtc` 시간 기준을 적용한다. - DTO를 UI model/state로 변환하고, matching nested payload null news 숨김, `PHOTO_CONTENT` label, `visibleFromAtUtc` 시간 기준을 적용한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingAuthHeader.kt` - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingAuthHeader.kt`
- blank token이면 `null`, 값이 있으면 `Bearer {token}`을 반환한다. - blank token이면 `null`, 값이 있으면 `Bearer {token}`을 반환한다.
- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingViewModel.kt` - Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingViewModel.kt`
@@ -634,6 +636,88 @@
--- ---
### Phase 9: 최근 소식 Response nested payload 계약 반영
- [x] **Task 9.1: 최근 소식 새 응답 계약 RED 테스트 추가**
- 수정:
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingMapperTest.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt`
- 작업:
- `FollowingNewsResponse` fixture를 새 nested payload 구조로 교체한다.
- `CREATOR_RANKING``creatorRanking.rank`, `creatorRanking.creatorId`, `creatorRanking.nickname`, `creatorRanking.profileImageUrl`로 ranking UI item을 만드는지 검증한다.
- `CONTENT_RANKING``contentRanking.rank`, `contentRanking.contentId`, `contentRanking.contentImageUrl`, `contentRanking.title`로 ranking UI item을 만드는지 검증한다.
- `AUDIO_CONTENT``audioContent`, `PHOTO_CONTENT``photoContent` payload로 content UI item을 만드는지 검증한다.
- `COMMUNITY_POST``communityPost.postId`, `creatorProfileImage`, `creatorNickname`, `imageUrl`, `content`, `createdAt`, `likeCount`, `commentCount`를 UI item에 반영하는지 검증한다.
- `type`에 대응하는 payload가 null인 최근 소식은 `recentNews` section에서 제외되는지 검증한다.
- `visibleFromAtUtc`는 기존처럼 `UtcRelativeTimeTextFormatter`에 전달되는지 검증한다.
- source test로 `HomeFollowingModels.kt`에 기존 평면 필드(`creatorProfileImageUrl`, `creatorNickname`, `title`, `body`, `thumbnailImageUrl`, `targetId`, `occurredAtUtc`, `rank`)가 `FollowingNewsResponse` 직접 필드로 남지 않는지 확인한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingMapperTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"`
- Expected: DTO/UI model/mapper 변경 전 RED 실패.
- Result: RED 확인. `FollowingCommunityPostNewsResponse`, `FollowingContentNewsResponse`, `FollowingContentRankingNewsResponse`, `FollowingCreatorRankingNewsResponse` 미정의와 `FollowingNewsResponse` nested parameter 미지원, 신규 UI item 필드 부재로 `compileDebugUnitTestKotlin`이 실패했다.
- [x] **Task 9.2: DTO와 UI model을 nested payload 기준으로 갱신**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/data/HomeFollowingModels.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingUiModels.kt`
- `app/src/main/res/values/strings.xml`
- `app/src/main/res/values-en/strings.xml`
- `app/src/main/res/values-ja/strings.xml`
- 작업:
- `FollowingNewsResponse` 직접 필드는 `newsId`, `type`, `visibleFromAtUtc`, `creatorRanking`, `audioContent`, `photoContent`, `contentRanking`, `communityPost`만 유지한다.
- `FollowingCreatorRankingNewsResponse`, `FollowingContentNewsResponse`, `FollowingContentRankingNewsResponse`, `FollowingCommunityPostNewsResponse` DTO를 추가하고 모두 `@Keep`, `@SerializedName`을 적용한다.
- `HomeFollowingNewsUiItem`은 새 payload 차이를 표현할 수 있도록 ranking/content/community variant를 분리하거나 기존 variant를 최소 확장한다.
- ranking UI item은 `rank`, target id, 표시 이미지 URL, 표시 제목/이름, `visibleFromAtUtc`, `visibleFromText`를 가진다.
- content UI item은 `contentId`, `contentImageUrl`, `title`, `creatorProfileImageUrl`, `creatorNickname`, label resource id, `visibleFromAtUtc`, `visibleFromText`를 가진다.
- community UI item은 `postId`, `creatorProfileImageUrl`, `creatorNickname`, `imageUrl`, `content`, `createdAt`, `likeCount`, `commentCount`, `visibleFromAtUtc`, `visibleFromText`를 가진다.
- `CONTENT_RANKING` 문장형 message가 필요하면 `screen_home_following_content_ranking_news_message`를 3개 locale string resource에 추가한다.
- 검증:
- Run: `./gradlew :app:mergeDebugResources`
- Expected: string resource가 중복 없이 merge된다.
- Run: `./gradlew :app:compileDebugKotlin`
- Expected: DTO/UI model 변경 후 컴파일 오류가 mapper/adapter 미반영 지점으로만 제한된다.
- Result: PASS. `FollowingNewsResponse` 직접 필드를 nested payload 계약으로 교체하고, ranking/content/community UI model variant 및 3개 locale content ranking message string을 추가한 뒤 최종 `mergeDebugResources`, `compileDebugKotlin`이 통과했다.
- [x] **Task 9.3: mapper와 최근 소식 adapter 바인딩 갱신**
- 수정:
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/model/HomeFollowingMappers.kt`
- `app/src/main/java/kr/co/vividnext/sodalive/v2/main/home/ui/HomeFollowingNewsAdapter.kt`
- `app/src/test/java/kr/co/vividnext/sodalive/v2/main/home/HomeFollowingFragmentSourceTest.kt`
- 작업:
- mapper는 `type`별 matching payload만 읽고, payload가 null이면 `null`을 반환해 해당 item을 제외한다.
- `CREATOR_RANKING` adapter binding은 기존 `screen_home_following_creator_ranking_news_message``FeedRankHighlight` 정책을 유지하되 `creatorRanking.nickname`, `creatorRanking.rank`, `creatorRanking.profileImageUrl`을 사용한다.
- `CONTENT_RANKING` adapter binding은 content title, rank, content image로 `FeedRankView`를 구성하고 순위 텍스트만 highlight한다.
- `AUDIO_CONTENT`, `PHOTO_CONTENT` adapter binding은 `FollowingContentNewsResponse` payload의 content/creator 필드를 `FeedContentView`에 전달한다.
- `COMMUNITY_POST` adapter binding은 `FeedCommunityView``postId`, creator profile/nickname, content, image, `likeCount`, `commentCount`를 전달한다.
- 최근 소식 item click target id는 `creatorId`, `contentId`, `postId` 중 payload에 맞는 값을 사용한다.
- `visibleFromAtUtc` 기준 상대 시간 표시 정책은 유지한다.
- 코드 리뷰에서 확인한 `contentImageUrl` nullable 계약을 DTO/UI model에 반영하고, Feed 위젯에는 빈 문자열 fallback으로 전달한다.
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingMapperTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"`
- Expected: PASS.
- Result: PASS. 최초 병렬 Gradle 실행 중 Kotlin incremental cache 충돌과 source-level test 기대 문자열 불일치 2건을 확인했다. Gradle을 `--no-daemon` 순차 실행으로 전환하고 source test 기대 문자열을 새 `HomeFollowingNewsUiItem.Community` 분기 구조에 맞춘 뒤 focused test가 PASS했다.
- 2026-06-30 코드 리뷰 후 보완 검증: `contentImageUrl = null``CONTENT_RANKING`, `AUDIO_CONTENT` 최근 소식이 유지되는 mapper test를 추가했다. RED: DTO `contentImageUrl`이 non-null `String`이라 `compileDebugUnitTestKotlin` type mismatch 실패. GREEN: DTO/UI model을 `String?`로 변경하고 adapter에서 `.orEmpty()` fallback을 적용한 뒤 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingMapperTest"` PASS.
- [x] **Task 9.4: 최근 소식 Response 변경 통합 검증**
- 검증:
- Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`
- Expected: 팔로잉 관련 local unit/source test가 모두 PASS.
- Run: `./gradlew :app:mergeDebugResources`
- Expected: layout/resource merge PASS.
- Run: `./gradlew :app:compileDebugKotlin`
- Expected: Kotlin compile PASS.
- Run: `./gradlew :app:ktlintCheck`
- Expected: ktlint PASS 또는 기존 unrelated failure만 발생.
- Run: `git diff --check`
- Expected: whitespace error 없음.
- 검증 기록:
- 2026-06-30 Phase 9 focused 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingMapperTest" --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"` PASS.
- 2026-06-30 Phase 9 통합 검증: `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew --no-daemon :app:mergeDebugResources`, `./gradlew --no-daemon :app:compileDebugKotlin`, `./gradlew --no-daemon :app:ktlintCheck`, `git diff --check` 모두 PASS. `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다.
- 2026-06-30 Phase 9 검증 참고: 최초 병렬 Gradle 검증은 Kotlin incremental cache `Storage ... is already registered` 충돌과 timeout으로 중단되어, 이후 모든 Gradle 검증은 `--no-daemon` 순차 실행으로 재검증했다.
- 2026-06-30 Phase 9 코드 리뷰 후 재검증: `contentImageUrl` nullable 계약을 보완한 뒤 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew --no-daemon :app:mergeDebugResources`, `./gradlew --no-daemon :app:compileDebugKotlin`, `./gradlew --no-daemon :app:ktlintCheck`, `git diff --check` PASS. `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다.
---
## Verification Log ## Verification Log
- 2026-06-25 Phase 1-3 구현 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` 모두 PASS. - 2026-06-25 Phase 1-3 구현 검증: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` 모두 PASS.
- 2026-06-25 Phase 1-3 코드 리뷰 및 재검증: blocking finding 없음. `./gradlew :app:mergeDebugResources`는 최초 sandbox lock 권한 오류 후 승인 실행으로 PASS했고, 나머지 검증 명령도 PASS. - 2026-06-25 Phase 1-3 코드 리뷰 및 재검증: blocking finding 없음. `./gradlew :app:mergeDebugResources`는 최초 sandbox lock 권한 오류 후 승인 실행으로 PASS했고, 나머지 검증 명령도 PASS.
@@ -648,3 +732,5 @@
- 2026-06-30 content feed clipping 위치 수정 검증: `docs/agent-guides/code-style.md`의 XML `android:clipToOutline` 금지 규칙에 맞춰 `view_feed_content.xml` 속성을 제거하고 `FeedContentView` Kotlin 코드에서 root `clipToOutline`/`outlineProvider`를 설정했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedViewTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `git diff --check` PASS. - 2026-06-30 content feed clipping 위치 수정 검증: `docs/agent-guides/code-style.md`의 XML `android:clipToOutline` 금지 규칙에 맞춰 `view_feed_content.xml` 속성을 제거하고 `FeedContentView` Kotlin 코드에서 root `clipToOutline`/`outlineProvider`를 설정했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.feed.FeedViewTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `git diff --check` PASS.
- 2026-06-30 팔로잉 크리에이터 `전체` 버튼 후속 검증: 팔로잉 크리에이터 RecyclerView 마지막에 `전체` item을 추가하고 `FollowingCreatorActivity` 이동을 연결했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"` RED/GREEN, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS. `mergeDebugResources`는 최초 sandbox lock 권한 오류 후 승인 실행으로 PASS했고, `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다. - 2026-06-30 팔로잉 크리에이터 `전체` 버튼 후속 검증: 팔로잉 크리에이터 RecyclerView 마지막에 `전체` item을 추가하고 `FollowingCreatorActivity` 이동을 연결했다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"` RED/GREEN, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS. `mergeDebugResources`는 최초 sandbox lock 권한 오류 후 승인 실행으로 PASS했고, `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다.
- 2026-06-30 팔로잉 크리에이터 `전체` 버튼 높이 보정 후 재검증: `전체` item root는 `wrap_content` 높이로 두고 invisible nickname spacer로 creator item 전체 높이를 맞췄다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS. `mergeDebugResources`는 최초 sandbox lock 권한 오류 후 승인 실행으로 PASS했고, `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다. - 2026-06-30 팔로잉 크리에이터 `전체` 버튼 높이 보정 후 재검증: `전체` item root는 `wrap_content` 높이로 두고 invisible nickname spacer로 creator item 전체 높이를 맞췄다. `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.HomeFollowingFragmentSourceTest"`, `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew :app:mergeDebugResources`, `./gradlew :app:compileDebugKotlin`, `./gradlew :app:ktlintCheck`, `git diff --check` PASS. `mergeDebugResources`는 최초 sandbox lock 권한 오류 후 승인 실행으로 PASS했고, `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다.
- 2026-06-30 Phase 9 최근 소식 Response nested payload 계약 반영 검증: `FollowingNewsResponse``newsId`, `type`, `visibleFromAtUtc`, type별 nested payload만 갖는 구조로 갱신하고 mapper/adapter를 matching payload 기준으로 변경했다. RED: 새 nested DTO/UI 필드 미구현으로 `compileDebugUnitTestKotlin` 실패. GREEN: focused test PASS. 통합 검증 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew --no-daemon :app:mergeDebugResources`, `./gradlew --no-daemon :app:compileDebugKotlin`, `./gradlew --no-daemon :app:ktlintCheck`, `git diff --check` 모두 PASS. `ktlintCheck`에서는 기존 `.editorconfig disabled_rules` deprecation warning만 출력됐다.
- 2026-06-30 Phase 9 코드 리뷰 후 nullable 계약 보완 검증: PRD의 `contentImageUrl: String?` 계약과 구현의 non-null DTO/UI model 불일치를 수정했다. RED: null image fixture 추가 시 `compileDebugUnitTestKotlin` type mismatch 실패. GREEN: `contentImageUrl` nullable DTO/UI model과 adapter `.orEmpty()` fallback 적용 후 focused mapper test PASS. 통합 재검증 `./gradlew --no-daemon :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.main.home.*Following*"`, `./gradlew --no-daemon :app:mergeDebugResources`, `./gradlew --no-daemon :app:compileDebugKotlin`, `./gradlew --no-daemon :app:ktlintCheck`, `git diff --check` PASS.