Skip to content

[fix] #702 임시저장·수신 목록에 재진입 갱신을 연결한다 - #900

Merged
Sadturtleman merged 4 commits into
developfrom
fix/702-refresh-wiring
Aug 24, 2026
Merged

Sadturtleman merged 4 commits into
developfrom
fix/702-refresh-wiring

Conversation

@Sadturtleman

Copy link
Copy Markdown
Collaborator

📌𝘐𝘴𝘴𝘶𝘦𝘴

Closes #702

📎𝘞𝘰𝘳𝘬 𝘋𝘦𝘴𝘤𝘳𝘪𝘱𝘵𝘪𝘰𝘯

이슈가 지목한 두 화면에 ON_RESUME 결선을 붙였습니다. 마인드레코드 홈(HomeScreen.kt:108)이 쓰는 패턴 그대로입니다.

refresh() = load() 를 그대로 부르지 않았습니다. ON_RESUME 은 화면 off/on·홈 버튼 복귀에서도 발화하는데, load() 는 로딩을 방출해 보고 있던 목록·선택·필터를 스피너로 갈아치웁니다. 홈이 refreshOnReturn() 을 따로 둔 이유와 같아서 두 VM 에도 같은 규칙을 넣었습니다.

  • 로딩 미방출
  • 재진입 갱신 실패 시 기존 화면 유지 (오류로 갈아치우지 않음)
  • 최초 진입 중복 호출은 loadJob 으로 차단

⚠️ 이슈 본문과 다른 점

이슈는 두 화면 모두 "호출부 0건" 으로 적었는데, 수신 화면은 refresh() 를 쓰고 있었습니다 — ReceiverMindRecordScreen.kt:71 의 오류 화면 재시도 버튼입니다. 그래서 refresh() 를 지우지 않고 남겼습니다(재시도는 로딩을 보여야 맞습니다). 실제로 빠진 것은 재진입 결선뿐입니다.

📷𝘚𝘤𝘳𝘦𝘦𝘯𝘴𝘩𝘰𝘵 · 실측

임시저장 목록을 연 상태로 앱을 백그라운드에 보내고, 서버에서 직접 그 draft 를 지운 뒤 복귀했습니다.

목록 진입:  총 1개 | 데일리질문 | 작성일 2026. 08. 23. | 10년 뒤의 나에게 편지를 쓴다면 첫 문장은?
   ↓ HOME → DELETE /api/v1/daily-questions/17 (200) → 앱 복귀
복귀 후:    총 0개 | 임시 저장된 항목이 없습니다.

재조회가 실제로 나가고 서버 상태가 반영되는 것이 확인됩니다. 수정 전에는 총 1개 가 그대로 남았습니다.

💬𝘛𝘰 𝘙𝘦𝘷𝘪𝘦𝘸𝘦𝘳𝘴

두 화면 모두 재조회 함수는 있는데 화면이 부르지 않아, 떠났다 돌아와도 목록이 낡은
채로 남았다. 마인드레코드 홈이 쓰는 결선(ON_RESUME → refreshOnReturn)을 그대로 붙인다.

`refresh() = load()` 를 그대로 부르지 않는다 — ON_RESUME 은 화면이 살아 있는 채로
발화하므로 로딩을 방출하면 보고 있던 목록·선택·필터가 스피너로 갈아치워진다. 홈과 같이
로딩을 방출하지 않고 실패해도 기존 화면을 유지하는 `refreshOnReturn()` 을 따로 둔다.

수신 화면의 `refresh()` 는 오류 화면 재시도 버튼이 쓰고 있어 남긴다 — 그쪽은 보고 있던
것이 오류 문구뿐이라 로딩을 보여도 잃을 게 없다. (이슈는 호출부 0건으로 적었지만
`ReceiverMindRecordScreen.kt:71` 에서 쓰고 있었다.)

최초 진입의 중복 호출은 VM 이 든 `loadJob` 으로 막는다.

Closes #702

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@OnlyReadOneBook OnlyReadOneBook left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

머지 전 1건 수정이 필요합니다. 결선 방식 자체(ON_RESUME + refreshOnReturn(), 홈과 동일 패턴)는 정확합니다.

  1. [blocking] 재진입 갱신 '성공'이 수신 화면의 필터·정렬을 소리 없이 초기화합니다.
    • ReceiverMindRecordViewModel.load() onSuccess(:74-81)가 Success(dailyQuestions = emptyList(), diaries = emptyList()).withDerived()로 상태를 새로 만드는데, 이때 filter는 기본값 ReceiverMindRecordFilter()(NEWEST·기간 없음)이고 withDerived()는 this.filter로 파생합니다. develop에서는 load()가 init·Error 재시도에서만 돌아 필터 적용된 Success 위에서 실행될 경로가 없었고, 이 PR의 refreshOnReturn()이 그 경로를 처음 만듭니다.
    • 시나리오: 수신자가 정렬=과거순·기간 필터 적용 → 홈 버튼/화면 off → 복귀 → ON_RESUME → getAll 성공 → 필터·정렬·헤더 인디케이터가 조용히 초기화. 본문("load()는 보고 있던 목록·선택·필터를 갈아치웁니다")이 밝힌 설계 목적과 정면 충돌합니다 — 로딩만 안 보일 뿐 필터는 여전히 사라집니다. onSuccess에서 기존 Success의 filter를 승계해 주세요.
  2. [normal] DraftList 선택 모드 중 재진입 갱신이 유령 선택을 만들 수 있습니다.
    • selectedKeys는 Screen의 remember(DraftListScreen.kt:74)라 갱신에도 유지됩니다 — 백그라운드 중 서버에서 지워진 항목의 키가 남아 "총 N개 선택"이 과대 표시되고, 선택 삭제 시 잔존 키가 필터링돼 빈 리스트로 delete()가 불리는데 base 동작상 무조건 "삭제되었습니다" 토스트가 뜹니다(#896 머지 시 빈 리스트 가드로 절반 완화, 카운트 과대 표시는 남음). 갱신 성공 시 selectedKeys를 현재 목록 키와 교집합으로 정리하기를 권합니다.

검증:

  • CI 전 체크 green
  • LifecycleEventEffect(ON_RESUME) 결선이 develop HomeScreen.kt:95-110의 기존 패턴·주석 근거(rememberUpdatedState 캡처)와 동일함을 확인 — 네비 복귀·백그라운드 복귀 모두 발화
  • 최초 진입 중복 로드는 loadJob?.isActive 가드로 차단, showsLoading=false라 로딩 미방출(깜빡임 없음), 갱신 실패 시 기존 목록 유지(두 VM 대칭) 확인
  • 기존 refresh()(오류 재시도, 로딩 방출) 보존 확인 — 본문 "이슈와 다른 점" 서술과 일치, 추억 공간 보류는 이슈 노트(#559 선행)와 정합
  • 참고: #904(retry() 리네임)·#896(deleteOutcome 개편)과 DraftListViewModel·Screen을 공유해 머지 순서상 리베이스 충돌이 예상됩니다

@OnlyReadOneBook

Copy link
Copy Markdown
Collaborator

리뷰 [blocking]의 에뮬레이터 재현을 시도한 경과를 남깁니다 — 재현은 미완이고, blocking 판정은 코드 근거(develop에는 filter 적용된 Success 위에서 load()가 돌 경로가 없고 이 PR의 refreshOnReturn()이 그 경로를 처음 만든다는 점 + onSuccess의 기본 filter Success 재생성)로 유지합니다.

수신자 화면 도달 경로(Welcome → 받은 기록함 → 발신자 등록 → 수신자 인증)를 이메일 인증(수신자 레코드 등록 이메일 실측 확인)·마스터 키까지 통과했으나, 증빙 서류 업로드 단계가 develop의 별개 실버그로 원천 불가라 화면에 도달하지 못했습니다:

  • BE POST /receiver-auth/presigned-url이 contentLength를 필수화했는데 FE DTO는 extension만 보내 400 → 앱 토스트 "서류 업로드에 실패했습니다" (재현 2회, curl로 필드 유무 대조 확정) → #915로 분리 등록 (fix(afternote): 수신자 증빙 서류 업로드가 완료되지 않아 열람 신청 진행 불가 #740 재발)
  • curl로 업로드·신청 제출을 대행해 PENDING까지 도달했으나 승인(담당자) 수단이 없어 이후 화면 진입 불가. 로컬 발신자 카드가 재시작에 소실되는 문제는 #599로 기등록.

실측 기록: .codex/qa-evidence/emulator/ae2e0926….json

OnlyReadOneBook and others added 2 commits August 24, 2026 14:47
load() 는 새 Success 를 만들어 상태를 갈아치우는데, 종전에는 init·오류 재시도에서만
돌아 «필터가 적용된 Success 위에서 실행될» 경로가 없었다. 이 PR 의 refreshOnReturn()
이 그 경로를 처음 만들어, 화면 off/on 한 번에 정렬·기간 필터가 조용히 초기화됐다.
로딩을 안 보이게 한 것만으로는 «갈아치우지 않는다» 가 되지 않는다.

임시저장 선택 모드의 유령 선택도 정리한다. selectedKeys 는 화면 remember 라 갱신에도
살아남아, 그 사이 서버에서 지워진 항목의 키가 남으면 «총 N개 선택» 이 과대 표시되고
선택 삭제가 빈 목록으로 나간다. 갱신된 목록과의 교집합으로 좁힌다.

리뷰 지적.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Sadturtleman

Copy link
Copy Markdown
Collaborator Author

지적 2건 고쳤습니다. 1번은 정확합니다 — 본문이 «load() 는 보고 있던 목록·선택·필터를 갈아치웁니다» 라고 적어 놓고, 로딩만 감추고 갈아치우는 것 자체는 그대로 뒀습니다.

1. [blocking] 재진입 갱신이 필터·정렬을 초기화 — onSuccess 에서 기존 Success.filter 를 승계합니다.

val currentFilter =
    (_uiState.value as? ReceiverMindRecordUiState.Success)?.filter
        ?: ReceiverMindRecordFilter()

ReceiverRefreshFilterTest 를 붙였고, 승계를 빼면 잡힙니다.

필터 승계 제거 → AssertionError: 갱신해도 보고 있던 필터가 남는다
  expected:<ReceiverMindRecordFilter(sortOrder=OLDEST, fromDate=2026-08-01, …)>
원복 → 통과

2. [normal] 선택 모드의 유령 선택 — 갱신된 목록 키와의 교집합으로 좁히고, 남는 게 없으면 선택 모드도 해제합니다.

val visibleKeys = (uiState as? DraftListUiState.Success)?.items?.map { it.key() }?.toSet()
LaunchedEffect(visibleKeys) {
    if (visibleKeys != null) {
        selectedKeys = selectedKeys intersect visibleKeys
        if (selectedKeys.isEmpty()) selectionMode = false
    }
}

카운트 과대 표시와 빈 목록 delete() 가 둘 다 닫힙니다.

머지 순서 참고 — 지적하신 대로 #904·#896 과 DraftListViewModel·Screen 을 공유합니다. #896 은 오늘 머지됐고, #904 는 방금 리뷰 반영해 올렸습니다. 이 브랜치도 develop 을 다시 받아 «오류 재시도(로딩 방출)» 와 «재진입 갱신(미방출)» 이 둘 다 남는지 확인하겠습니다.

# Conflicts:
#	feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/receiver/ReceiverMindRecordScreen.kt
#	feature/mindrecord/presentation/src/main/kotlin/com/afternote/feature/mindrecord/presentation/screen/sender/DraftListScreen.kt

@OnlyReadOneBook OnlyReadOneBook left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

승인 — 지적 2건 해소

88b5271c 로 재확인했다.

1. [blocking] 재진입 갱신이 필터·정렬을 초기화 — 해소. onSuccess 가 기존 Success.filter 를 승계한다. 가드가 실제로 잡는 것도 직접 확인했다.

필터 승계 두 줄 제거
→ ReceiverRefreshFilterTest > 재진입 갱신이 정렬과 기간 필터를 지운다면 안 된다 FAILED
원복 → 통과

테스트가 «필터를 적용한 뒤 refreshOnReturn() 을 호출한다» 는 실제 순서를 그대로 재현하고, KDoc 이 «종전에는 load() 가 init·재시도에서만 돌아 이 경로가 없었고 이 PR 이 그 경로를 처음 만든다» 는 배경까지 남긴 것이 좋다. 다음에 load() 를 만지는 사람이 왜 승계가 필요한지 되짚지 않아도 된다.

2. [normal] 선택 모드의 유령 선택 — 해소. 갱신된 목록 키와의 교집합으로 좁혀 카운트 과대 표시와 빈 목록 delete() 가 함께 닫혔다.

승인에 지장 없는 것 하나

선택 모드 해제 조건이 «교집합 결과가 비었을 때» 라, 선택이 원래 없던 경우까지 모드가 풀린다. 목록 내용이 바뀔 때만 LaunchedEffect 가 다시 도니 자주 나타나진 않지만, 「선택 모드로 들어가고 아직 아무것도 고르지 않은 채 화면을 다녀오는 사이 항목이 하나 늘어난」 경우가 그 자리다.

val hadSelection = selectedKeys.isNotEmpty()
selectedKeys = selectedKeys intersect visibleKeys
// 고른 것이 전부 사라졌을 때만 모드를 푼다 — 원래 안 고른 상태는 사용자가 만든 것이다.
if (hadSelection && selectedKeys.isEmpty()) selectionMode = false

검증

로컬 워크트리, head 88b5271c.

:feature:mindrecord:presentation:testDebugUnitTest → 46 tests, 0 failures
:app:compileDebugUnitTestKotlin
ktlintCheck (전 모듈)
→ BUILD SUCCESSFUL

CI 전부 success — Ktlint · Unit Tests · Android Lint · Validate Compose Preview Screenshots · CodeQL · dependency-review · guard. develop 과 충돌도 없다(merge-tree 실측).

머지 순서 — 직전 리뷰에서 걱정했던 3파전은 정리됐다. #896 은 머지됐고, 이 브랜치는 develop 을 다시 받아 «오류 재시도(로딩 방출)» 와 «재진입 갱신(미방출)» 이 둘 다 살아 있다. 남은 것은 #904 인데 그쪽은 DraftListScreen 을 건드리지 않아(리스트 화면 2종과 두 ViewModel 만) 이 PR 과 겹치지 않는다.

QA · 시안 대조 — 시각 변경이 없는 결선 수정이고, 이번 두 수정은 상태 승계·집합 연산이라 단위 테스트로 판정 가능한 성질이다. 앞선 리뷰에서 수신 화면 실기동은 열람 승인(관리자 전용) 때문에 막혀 있었고 그 사정은 그대로다 — .codex/qa-evidence/emulator/ae2e0926….json 에 기록해 둔 그대로다. 시안 대조 해당 없음.

@Sadturtleman
Sadturtleman merged commit 6e23a6a into develop Aug 24, 2026
13 checks passed
@Sadturtleman
Sadturtleman deleted the fix/702-refresh-wiring branch August 24, 2026 12:55
@github-actions

Copy link
Copy Markdown
Contributor

🚀 QA 배포 권장

QA 포인트

이 판단은 배포를 자동 실행하지 않습니다. deploy일 때 Release Distribution을 수동 실행하세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(mindrecord): 임시저장·수신 목록의 refresh() 가 미결선 — 정의만 있고 호출부 0건

2 participants