Repository navigation
refactor(afternote): 편집기 MVI와 소비 경계를 통합한다 (#1804) - #1972
Conversation
|
열린 PR 중 이 PR 과 같은 파일을 서로 다르게 고친 것이 있다. 어느 쪽이 먼저 머지되든 나중 쪽은 base 를 병합해야 통과한다 (#1750).
머지 자체는 GitHub 이 막는다 — 한쪽이 들어가면 다른 쪽은 CONFLICTING 이 되어 merge queue 에 못 들어간다. 이 코멘트는 리컨사일러가 매 실행 현재 상태로 고쳐 쓴다. 해소되면 |
|
📸 CI 표준 환경에서 screenshot baseline 2개를 생성·검증해 |
Sadturtleman
left a comment
There was a problem hiding this comment.
MVI 전환 자체는 문제 없습니다. reduce 가 순수하고 IO·계측은 onIntent 쪽에 남았으며, dispatchForm 이 스냅샷 저장을 리듀서 밖으로 뺀 것도 맞습니다. toAfternoteEditorError 단위 테스트를 저장 동작 테스트로 옮긴 전환도 좋습니다. 다만 접근성 가드 하나가 설명 없이 사라져 그 부분만 요청합니다.
EditorServiceSelectionSheetTest에서assertAccessibleClickTargets()가 빠졌다. 이 시트 전체의 48dp·이름·role·중첩 클릭 계약(#1167·#1179·#1669)을 보던 유일한 지점입니다. 남은 단언은 서비스 행 2개의 크기·role 뿐이라 검색 입력·빈 결과 안내·시트 컨테이너는 이제 아무도 보지 않습니다. PR 본문에도 이 제거가 없습니다.- 원인은 시트 진입점 교체입니다. 테스트를
EditorServiceSelectionSheetContent대신EditorServiceSelectionSheet(visible = true, ...)로 바꾸면서 Material3 가 만드는 스크림 노드가 스캔에 들어옵니다. 로컬에서 되살려 돌려 보면 실패는 이것 하나입니다 —Role 누락: #0 touch=320.0.dp×470.0.dp name="Close sheet". 우리 화면 코드의 위반이 아니라 프레임워크 노드입니다. - 둘 중 하나로 되살려 주세요. (1) 렌더 seam 을 유지해 시트 본문을 직접 그리고 그 위에서 스윕을 돌리거나 —
core:ui의MediaSelectSheetContent·MediaSelectSheetContentTest가 저장소의 기존 패턴입니다 — (2)assertAccessibleClickTargets에 제외 훅을 추가해 스크림만 빼고 스윕을 유지해 주세요. 되살릴 수 없다면 그 이유를 테스트 KDoc 과 PR 본문에 남겨 주세요. - 참고. seam 을 유지하면 프로덕션의
LocalInspectionMode분기도 함께 사라집니다. 지금은EditorServiceSelectionSheet가 inspection 여부에 따라 다른 트리를 그려, screenshot 이 검증하는 경로와 실제 앱 경로가 갈립니다.
|
지적한 접근성 스윕을 bce5e81 에서 되살렸습니다. 두 방법 중 (2) 로 갔습니다. assertAccessibleClickTargets 에 exclude 훅을 추가하고 시트 테스트는 compose ui 의 close_sheet 문자열(Material3 스크림의 contentDescription 출처)을 가진 노드만 뺍니다. 검색 입력·빈 결과 안내·행의 48dp·이름·role·중첩 클릭 계약은 그대로 스윕합니다. 제외한 노드 말고는 스윕이 유지된다는 회귀를 TouchTargetAssertionsTest 에 추가했고, 본문에도 적었습니다. core:ui 와 afternote presentation 의 해당 테스트·ktlint 를 로컬에서 통과했습니다. |
bce5e81 to
f6e9028
Compare
|
음성 첨부 PR #1625 가 시안 근거 없음으로 닫혀(#1979) 이 브랜치를 그 커밋 없이 다시 쌓았습니다. 이전 tip 이 tip 에서 afternote domain / data / presentation 단위 테스트 19 / 100 / 338 건 통과(실패·오류·skip 0), 세 모듈 ktlintCheck, 앱 main·unitTest·androidTest Kotlin 컴파일을 확인했습니다. 다른 변경은 없습니다. |
…or-mvi # Conflicts: # feature/afternote/presentation/src/main/kotlin/com/afternote/feature/afternote/presentation/editor/AfternoteEditorViewModel.kt
Closes #1804
Refs #1968, #1732
편집기의 폼·미디어·수신자·저장·prefill 입력과 소비 호출을
onIntent로 모으고, 상태 변경을MviViewModel의 순수 reducer로 옮깁니다. 기존EditorFormMutations규칙을 재사용하며 SavedState 직렬화·저장소 호출·계측은 reducer 밖에 둡니다.직접 base는 #1971의
refactor/1902-memorial-photo입니다. 선행 순서는 #2164 → #2167 → #1837 → #1838 → #1971 → #1972입니다. 프로세스 복원 입력 보호 #1839는 이 최종 MVI 위에서ApplyPrefill과PrefillLoaded계약을 확장합니다. 기존 mutable 상태와 공개 함수를 먼저 확장했다가 다시 MVI로 옮기던 중간 구현을 제거했습니다.Nav3 공유 flow와 직접 화면 소비처를 Intent에 연결합니다. 오류 occurrence 소비, prefill 적용 전 skeleton, 부분 PATCH, 썸네일 재시도, 늦은 수신자 조회 결과 무시를 유지합니다. 서비스 시트와 이탈 경고 함수는 실제 프로덕션 호출 범위에 맞게 private로 줄이고 기존 화면 계약으로 테스트합니다. 접근성 검사는 Material3 스크림만 제외하고 실제 클릭 대상 검증을 유지합니다.
기존 나머지 8개 ViewModel은 #2021의 독립 후속 범위입니다. 이 PR은 현재 제공되는 에디터 경계만 다루며 미확정 임시저장 화면의 구현을 예약하지 않습니다.
검증
JVM 검증 리비전
ee6fa1f642e2ee29b20f67e6f9c56e2fdcea26ca에서 presentation 345개, domain 19개, data 98개 테스트가 통과했습니다(실패·오류·건너뜀 0). presentation ktlint, 실제 base 대비 전체 diff의 모듈 소유권 검사와git diff --check를 통과했습니다.현재 HEAD
44d6dc33e5edb585de68883450009211f53fbdca는 검증 리비전에서 수신자 복귀 KDoc만 최종 Intent 이름으로 정정했습니다. 프로덕션 코드와 테스트 코드는 동일하며 KDoc 링크 검사(끊김 0)와 ktlint를 통과했습니다.🧪 CI Test Plan
{ "androidTest": { "mode": "selected", "reason": "EditorFlowHost와 공유 ViewModel의 수신자 선택 결과 소비를 Intent로 바꾸므로 실제 앱 Nav3의 선택·해제·취소 복귀와 빈 목록/실패/재시도를 API 30에서 확인한다. 저장·프리필·복원·요청 경합과 화면 이탈 경고는 별도 JVM/Compose 회귀로 검증한다.", "tests": [ { "path": "app/src/androidTest/java/com/afternote/afternote_fe/ReceiverSelectionResultAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverSelectionResultAndroidTest#afternoteEditorNavHost_receiverSelectOpensWithFormReceiverCheckedAndConfirmAddsOnlyTheChosen", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/ReceiverSelectionResultAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverSelectionResultAndroidTest#afternoteEditorNavHost_receiverSelectUncheckingFormReceiverRemovesItFromEditorOnConfirm", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/ReceiverSelectionResultAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverSelectionResultAndroidTest#afternoteEditorNavHost_receiverSelectBackKeepsEditorReceiversUnchanged", "device": "api30" }, { "path": "app/src/androidTest/java/com/afternote/afternote_fe/ReceiverSelectionResultAndroidTest.kt", "selector": "com.afternote.afternote_fe.ReceiverSelectionResultAndroidTest#afternoteSelectReceiver_emptyAndLoadFailureBlockConfirmUntilRetryLoadsList", "device": "api30" } ] } }