Skip to content

마음의 기록 허브를 Navigation 3 로컬 스택으로 — #1697 파일럿 #959

Closed

Description

@OnlyReadOneBook

#1697이 전체 마음의 기록 entry 계약으로 확장하기 전에 구현 비용과 함정을 검증하는 로컬 Nav3 파일럿이다. #924는 App Links 계약을, #1702는 최종 앱 루트 전환을 각각 소유한다.

왜 mindrecord 가 파일럿인가

전 그래프 중 유일하게 파일럿 조건을 충족한다 (커밋 104462fd 기준 실측).

그래프 popUpTo 그래프 스코프 공유 VM 파일럿 적합
mindrecord 0 0 ✅
afternote(작성자) 2 AfternoteHostViewModel ❌ 지문 게이트가 진입점
onboarding 3 SignUp · FindId ❌
receiver 4 DeliveryVerificationFlow ❌ nested flow VM
setting 2 SettingViewModel ❌
timeletter 0 TimeLetterWrite · Timeletter ❌

순수 push/pop 뿐이라 Nav3의 최대 난관인 복잡한 그래프 스코프 VM 이관을 건드리지 않고 전환 비용만 측정할 수 있다.

범위

Route.MindRecord 안에 로컬 NavDisplay 백스택을 두고, 허브 내부 화면 3개를 그 안으로 옮긴다. 루트 NavHost(Nav2)에는 Route.MindRecord 하나만 남는다.

  • 로컬 스택 편입: 허브(HomeScreen) · 데일리질문 작성 · 일기 작성 · 임시저장 목록
  • MindRecordRoute 를 NavKey 로 (+ 허브 키 추가)
  • MindRecordNavActions 8개 → 1개 (onMemorySpaceBack 만 남음, push/pop 위임 7개 소멸)
  • DiaryWriteViewModel 라우트 인자 주입 경로 교체 (아래 함정 3)
  • 바텀바 판정에 로컬 스택 깊이 반영
  • Route.MemorySpace · Route.ReceiverMindRecord 는 Nav2 잔류 — IA 상 허브 외부 진입점

선행 검증 (0823 실측 — 이 이슈 착수자가 다시 안 해도 되는 것)

전환 가능성 확인을 위해 프로토타입을 만들어 에뮬레이터(Pixel_7_Claude_QA, 실서버 QA823 계정)로 돌려봤고, 전 시나리오 통과했다.

시나리오 결과
허브 진입 · 카테고리 탭(데일리질문/일기/주간리포트) ✅
작성 화면 push → 바텀바 숨김 ✅
저장 성공 → pop → 허브 복귀 + 바텀바 재등장 ✅
4단 스택 (허브→작성→임시저장 목록→이어쓰기) ✅
back 체인 pop ✅
경계 back — 허브에서 back 이 루트 NavHost 로 전파 → 홈탭 ✅ (#1702가 최종 루트에서 보존해야 할 최대 리스크)
이어쓰기 인자(draftId) 프리필 ✅
pop 시 엔트리 VM clear (재진입 시 입력값 잔존 없음) ✅
탭 이탈 후 복귀 — 허브·선택 카테고리 복원 ✅

빌드 게이트도 통과: 컴파일 · :app:compileDebugUnitTestKotlin · mindrecord 유닛테스트 · konsist · ktlint.

미검증 1건: predictive back 미리보기 애니메이션 (원격 조작 한계 — 제스처 홀드 재현 불가). 실기기에서 확인 필요.

구현 함정 3가지 (실측으로 걸린 것)

1. NavDisplay.onBack 은 () -> Unit 이다
문서·블로그에 도는 onBack = { count -> ... } (Int 인자) 형태는 이 버전에서 컴파일 에러. onBack = { backStack.removeLastOrNull() }.

2. entryDecorators 를 넘기면 기본 목록을 통째로 대체한다
rememberSaveableStateHolderNavEntryDecorator() 를 직접 포함하지 않으면 rememberSaveable 상태 보존이 조용히 깨진다. VM 스코프용 rememberViewModelStoreNavEntryDecorator() 와 둘 다 넣어야 한다.

3. DiaryWriteViewModel — SavedStateHandle 라우트 주입이 사라진다
Nav3 엔트리에는 Nav2 의 savedStateHandle.toRoute<T>() 자동 채움이 없다. assisted 주입으로 바꾸면 타입 안전하게 대체된다:

@HiltViewModel(assistedFactory = DiaryWriteViewModel.Factory::class)
class DiaryWriteViewModel @AssistedInject constructor(
    @Assisted private val route: MindRecordRoute.DiaryWriteRoute,
    // …
) : ViewModel() {
    @AssistedFactory
    interface Factory { fun create(route: MindRecordRoute.DiaryWriteRoute): DiaryWriteViewModel }
}

엔트리 쪽:

entry<MindRecordRoute.DiaryWriteRoute> { key ->
    val viewModel = hiltViewModel<DiaryWriteViewModel, DiaryWriteViewModel.Factory>(
        creationCallback = { factory -> factory.create(key) },
    )
    DiaryWriteScreen(viewModel = viewModel, /* … */)
}

부수 효과로 DiaryWriteScreen 의 viewModel 파라미터가 기본값을 잃어 호출부가 주입해야 하고, androidTest 의 VM 직접 생성이 SavedStateHandle(mapOf("draftId" to …)) → route = MindRecordRoute.DiaryWriteRoute(draftId = …) 로 오히려 단순해진다 (MindRecordFlowAndroidTest · TimeLetterMindRecordCompletionAndroidTest 2곳).

바텀바: AppState.shouldShowBottomBar 는 Nav2 destination 만 보므로 로컬 스택 깊이를 모른다. 호스트가 onHubDepthChanged(isAtHub) 로 앱 셸에 올려 판정에 합성하고, 탭 이탈 시 DisposableEffect 로 true 복원 (다른 탭 판정 오염 방지).

의존성

navigation3 = "1.1.6"   # 2026-08-12 stable
androidx-navigation3-runtime = { group = "androidx.navigation3", name = "navigation3-runtime", version.ref = "navigation3" }
androidx-navigation3-ui = { group = "androidx.navigation3", name = "navigation3-ui", version.ref = "navigation3" }
androidx-lifecycle-viewmodel-navigation3 = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelKtx" }

Nav3 는 1.0.0 stable 이 2025-11 출시, 현 stable 1.1.6. feature/mindrecord/presentation 에만 추가하고, 컨벤션 플러그인(afternote.android.navigation)의 Nav2 는 Route.MindRecord 루트 등록·라우트 직렬화에 계속 쓰이므로 공존한다.

충돌 지형 — 착수 시점 주의

착수 시점에 마음의 기록 열린 PR 이 많으면 (0823 기준 18건) MindRecordNavGraph · DiaryWriteViewModel · DiaryWriteScreen 이 광범위하게 바뀌므로 리베이스 비용이 크다. 열린 PR 을 먼저 정리한 뒤 착수하는 편이 낫다.

특히 #894 (홈 TODAY'S QUESTION CTA → 데일리질문 작성 화면 연결)는 작성 화면이 top-level 라우트라는 전제라 이 변경과 구조적으로 교차한다 — 홈에서 허브 내부 화면으로 바로 진입하는 경로는 로컬 스택 도입 후 초기 백스택 구성으로 다시 표현해야 한다.

완료 조건

  • 위 실측 시나리오 9개 재현 + predictive back 확인
  • :app:compileDebugUnitTestKotlin 포함 빌드 게이트 통과
  • #1697의 전체 entry 계약 이관 기준을 제공하며, 최종 결과는 #1702가 루트 전환에서 소비한다.

Activity

  1. changed the title [-]refactor(mindrecord): 마음의 기록 허브를 Navigation 3 로컬 스택으로 — #924 1단계 파일럿[/-] [+]마음의 기록 허브를 Navigation 3 로컬 스택으로 — #924 1단계 파일럿[/+] on Aug 28, 2026
  2. added
    refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
    area:mindrecord마음의 기록 기능 영역
    and removed
    enhancementNew feature or request
    on Aug 28, 2026
  3. changed the title [-]마음의 기록 허브를 Navigation 3 로컬 스택으로 — #924 1단계 파일럿[/-] [+]마음의 기록 허브를 Navigation 3 로컬 스택으로 — #1698 파일럿[/+] on Aug 31, 2026
  4. changed the title [-]마음의 기록 허브를 Navigation 3 로컬 스택으로 — #1698 파일럿[/-] [+]마음의 기록 허브를 Navigation 3 로컬 스택으로 — #1697 파일럿[/+] on Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:mindrecord마음의 기록 기능 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions