Repository navigation
fix(receiver): 수신자 홈 헤더에서 회원 설정 진입점을 내린다 (#613) - #1468
Conversation
톱니를 누르면 **회원 설정 화면**이 그대로 열렸고, 그 화면의 유일한 항목이 「로그아웃」이다. 수신자는 로그인한 적이 없는 사용자(`X-Auth-Code` 기반)라 지울 세션이 없다 — 눌렀을 때 무엇이 지워지는지도 불명확했다 (#613). 수신자용 설정 계약이 생기기 전까지 **새 화면이나 가짜 로그아웃을 지어내지 않고 진입점을 내린다.** #613 의 디자인 승인 게이트 해제(2026-08-28)가 정한 최소안 그대로다. 접근 종료· 인증 해제처럼 데이터·보안 계약이 필요한 동작은 여기서 신설하지 않는다. `HomeTopBar` 가 이미 `onSettingClick = null` 로 톱니를 감추는 계약을 갖고 있어(#1103) 새 분기를 만들 필요가 없었다. `ReceiverHomeActions` 에서도 그 콜백을 걷어, 배선이 되살아나면 컴파일이 막는다. 가드 두 개다. - `설정 아이콘을 그리지 않는다` — 종전의 「이름이 있고 눌린다」를 뒤집은 자리 - `헤더에 누를 수 있는 액션이 하나도 없다` — 이름을 모르는 **새 아이콘**이 추가되는 경우까지 잡는다. 앞의 단언만으로는 그때 조용히 통과한다 톱니를 되살리면 둘 다 FAILED 다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
앞 커밋이 계약에서 `onSettingClick` 을 지우면서 `app` androidTest 의 no-op 묶음이 컴파일되지 않았다. 이 파일은 `:app:compileDebugKotlin` 에 안 걸려 앞 커밋 검증에서 샜다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🚧 리뷰 적체 가드 — 이 PR 을 닫습니다리뷰 응답을 기다리는 PR 이 1건 남아 있습니다.
이 저장소는 리뷰 응답을 기다리는 PR 이 1건이라도 남아 있으면 새 PR 을 열 수 없습니다 (#1136). 각 PR 에 필요한 리뷰는 한 건입니다 — 누구든 먼저 판정을 내면 그 PR 은 모두의 목록에서 빠집니다. 승인 뒤 변경을 다시 볼 의무는 없고, 변경요청도 작성자가 같은 리뷰어에게 재요청한 뒤 반영 커밋을 올린 경우에만 다시 리뷰어의 몫이 됩니다. 되살리는 법위 리뷰를 끝낸 뒤 이 PR 을 다시 여세요. 브랜치는 지워지지 않았고 커밋도 그대로입니다. gh pr reopen 1468리뷰보다 이 PR 이 급하면 |
|
📸 CI 표준 환경에서 다시 렌더했지만 변경할 screenshot baseline 이 없습니다. |
1hyok
left a comment
There was a problem hiding this comment.
승인합니다. 진입점만 내리고 아무것도 새로 만들지 않은 것이 #613 디자인 게이트 해제(2026-08-28)와 문자 그대로 맞습니다 — 이슈 본문의 «수신자용 설정 계약이 생기기 전까지 새 설정 화면이나 가짜 로그아웃 동작을 만들지 않는다» 그대로입니다.
HomeTopBar 가 이미 갖고 있던 onSettingClick = null 계약(#1103)을 쓰고 새 분기를 만들지 않은 것, 그리고 ReceiverHomeActions 에서 콜백 자체를 걷어 배선이 되살아나면 컴파일이 막게 한 것이 이 PR 에서 제일 좋은 부분입니다. 다음 사람이 onSettingClick = {} 로 조용히 되돌릴 자리가 없습니다.
한 가지만 인라인에 적었습니다 — 새 가드의 범위가 이름보다 넓다는 것으로, 머지를 막지 않습니다.
검증
| 항목 | 결과 |
|---|---|
| 시안 대조 | 시안(수신자 열람 홈)에는 톱니가 있습니다. 그럼에도 제거가 맞다는 판단은 #613 디자인 게이트 해제가 명시적으로 정한 것이고, PR 본문이 그 근거를 정확히 인용했습니다. 시안 우선이 아니라 게이트 우선이 맞는 자리입니다 |
| screenshot baseline | 갱신본 3장을 로컬에서 꺼내 눈으로 확인 — 헤더에 로고만 남고 톱니가 사라졌습니다. 나머지 레이아웃은 그대로입니다. CI 봇 커밋(06:51)으로 반영돼 Validate Compose Preview Screenshots 도 green 입니다 |
| 실기 QA | 미실시 — 제거된 진입점의 «눌러도 지울 세션이 없다» 는 #613 이 이미 실기로 확인한 근거이고, 이 PR 은 그 진입점을 없앨 뿐이라 새로 확인할 동작이 없습니다 |
| CI | Base Freshness(develop 전진, 전 PR 공통) 외 전부 green. 머지 직전 update-branch 만 태우면 됩니다 |
| } | ||
| } | ||
|
|
||
| composeRule.onAllNodes(hasClickAction()).assertCountEquals(0) |
There was a problem hiding this comment.
[minor] 머지를 막지 않습니다 — 다만 이 단언은 «헤더» 가 아니라 화면 전체를 셉니다.
지금 통과하는 이유는 ReceiverHomeScreen 의 ReceiverHomeUiState.Loading -> { } 가 본문을 아무것도 그리지 않기 때문입니다. 즉 헤더만 남은 상태라 우연히 이름과 결과가 맞아떨어집니다. 두 방향으로 어긋납니다.
- 로딩에 스켈레톤 버튼이나 «취소» 하나만 생겨도, 헤더와 무관한 이유로 이 테스트가 red 가 됩니다.
- 반대로
Success상태에서만 톱니가 되살아나는 회귀는 못 잡습니다 — 두 테스트 다Loading만 태웁니다. 지금 코드는 상태와 무관하게 같은HomeTopBar한 곳이라 실질 구멍은 아니지만, 이 테스트가 막겠다고 적은 «이름을 모르는 새 아이콘» 은 그 헤더가 상태별로 갈리는 순간 빠져나갑니다.
HomeTopBar 에 이미 PROFILE_ICON_TEST_TAG 같은 앵커를 쓰는 선례가 있으니, 탑바 서브트리로 좁히거나(onNode(hasTestTag(...)).onChildren()) 최소한 Success 상태도 함께 태우면 이름과 실체가 맞습니다.
Closes #613
무엇을
수신자 홈 헤더의 회원 설정 진입점(톱니)을 내립니다. #613 남은 몫입니다 — 프로필 아이콘 제거(#877)와 헤더 액션 명명(#1103)은 이미 들어가 있습니다.
왜
톱니를 누르면 회원 설정 화면이 그대로 열리고, 그 화면의 유일한 항목이 「로그아웃」입니다. 수신자는 로그인한 적이 없는 사용자(
X-Auth-Code기반)라 지울 세션이 없습니다. 눌렀을 때 무엇이 지워지는지도 불명확합니다.#613 의 디자인 승인 게이트 해제(2026-08-28) 가 정한 최소안 그대로입니다.
그래서 이 PR 은 아무것도 새로 만들지 않습니다. 진입점만 내립니다.
어떻게
HomeTopBar가 이미onSettingClick = null로 톱니를 감추는 계약을 갖고 있어(#1103) 새 분기를 만들 필요가 없었습니다. 화면에서null을 넘기고,ReceiverHomeActions에서도 그 콜백을 걷었습니다 — 배선이 되살아나면 컴파일이 막습니다.ReceiverHomeScreen.ktonSettingClick = actions.onSettingClick→nullReceiverHomeActions.ktonSettingClick제거AppNavigationActions.ktrememberReceiverHomeActions의Route.Setting배선 제거가드
설정 아이콘을 그리지 않는다헤더에 누를 수 있는 액션이 하나도 없다톱니를 되살리면(
onSettingClick = {}) 둘 다 실패합니다.검증
:feature:receiver:presentation:testDebugUnitTest·ktlintCheck:app:compileDebugKotlin·:app:compileDebugAndroidTestKotlin·:app:ktlintCheckCI Test Plan
{ "androidTest": { "mode": "selected", "reason": "`AppNavigationActions` 의 수신자 홈 액션 배선에서 설정 목적지를 걷어내므로, 남은 진입점들이 여전히 정확한 목적지로 가는지 실제 NavHost 에서 확인한다. 헤더 렌더 자체는 Robolectric 과 screenshot baseline 이 맡는다.", "tests": [ { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.AppAndReceiverCompletionAndroidTest#invalidCredentials_correctedPasswordThenRetry_entersHomeWithoutReportingUserError", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverRuntimeCompletionAndroidTest#emailCodeExpired_resendAndNewCode_verifyExactlyOnce", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverRuntimeCompletionAndroidTest#documentSubmit_doubleTapWhileRequestInFlight_sendsOnePayload", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverRuntimeCompletionAndroidTest#documentSlot_replaceFailureKeepsPreviousThenSuccessReflectsReplacement", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverRuntimeCompletionAndroidTest#receiverVerificationActions_removeConsumedStepsAndCompletionReturnsToRecords", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverRuntimeCompletionAndroidTest#receivedGalleryDetail_routesGalleryContractInsteadOfSocialCredentials", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverRuntimeCompletionAndroidTest#receivedMemorialDetail_routesPlaylistContractAndForwardsExactId", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverRuntimeCompletionAndroidTest#homeActions_routeImplementedEntryPointsToExactDestinations", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverRuntimeCompletionAndroidTest#receiverHome_allFailureThenRetryPartialSuccess_keepsAvailableSectionsAndReportsBothStages", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/AppAndReceiverCompletionAndroidTest.kt", "selector": "com.afternote.afternote_fe.AppAndReceiverCompletionAndroidTest#welcomeCheckRecords_opensActualReceivedRecordsStartDestination", "device": "api30" } ] } }