compose-agent 4.2.1: Navigation 3 decision table#8
Conversation
Adds a "when to use" decision table at the top of navigation.md as a workflow entry point, mirroring the paging.md pattern. Covers Nav3 vs Nav2 type-safe vs plain state, argument passing via NavKey fields, ViewModel-driven navigation, per-entry ViewModel scope, BackHandler, deep links, and ListDetailSceneStrategy vs SinglePaneSceneStrategy. Includes an LLM tell against reaching for NavController/NavHost in new Nav3 code or pulling a nav library into a single stateful screen. Sourced from android/skills#50, which noted the Nav3 guidance was index-only without a workflow entry point. Versions: compose-agent -> 4.2.1; jetpack-compose-audit unchanged at 4.2.0 (bin/ci enforces per-plugin claude/cursor parity, not cross-plugin lockstep). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Zc7mbSrnbAsnVi3tSsBAs
…e caveats Round 1 relay (codex + cursor + antigravity) — no blockers. Fixes: - Navigation-from-ViewModel: the decision-table row and the existing guardrails / Common Mistakes now model pending navigation as durable UI state cleared after navigating (per flows.md / concurrency.md), reserving Channel<NavEvent> for genuinely ephemeral signals. Removes the conflict Codex flagged with the repo's own one-shot-event guidance and Android's UI events guidance. - README updated for 4.2.1 (header + What's new), per the repo's release convention that CHANGELOG points at README (Codex + Cursor). - Decision-table caveats (Cursor): "tabs" row clarified as in-screen tabs, not bottom-nav destinations (which are multi-destination -> Nav3); deep-link row marked Nav3, with a pointer to the Nav2 navDeepLink path below. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Zc7mbSrnbAsnVi3tSsBAs
Round 2 relay (codex + cursor + antigravity) — no blockers, "good to merge". Doc-consistency polish: - Back-interception row + Common Mistakes now name both stacks: NavDisplay.onBack (Nav3) / NavHost (Nav2), and navController (Nav2) / backStack (Nav3), so Nav2 readers aren't pointed at a Nav3-only API and Nav3 readers see the parallel "don't inject it into the ViewModel" mistake. - Deep-link row points at the scene-strategy registration path too, plus the Nav2 navDeepLink pointer. - Argument row gains a Nav2 type-safe parenthetical (fields via toRoute()). - compose-agent SKILL.md step 6 nudges "start from the decision table" for parity with the paging step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Zc7mbSrnbAsnVi3tSsBAs
…bels Round 3 relay (codex + cursor + antigravity) — no blockers across all rounds. Final accuracy + consistency pass: - Deep links: remove the inaccurate "register deep links via scene strategy" wording from both the decision table and the Deep Links section. Scene strategies choose which entries render for a window size; they do not register URIs. Nav3 deep-linking is resolve-intent-to-key-and-push (Codex + Cursor both flagged the invented-API risk). - Label Nav3-only rows explicitly (ListDetail/SinglePane scene strategies, rememberViewModelStoreNavEntryDecorator, NavKey arguments) so a Nav2 reader doesn't pick a Nav3 row — e.g. adding the entry decorator on a Nav2 graph where NavBackStackEntry already scopes viewModel() (Cursor #1). - Common Mistakes: add the Channel-for-ephemeral carve-out so the durable-state rule doesn't read as banning all channel-based nav (Cursor #3); name backStack alongside NavController in the hoisting mistake for Nav3 parity. - SKILL.md references blurb mirrors paging's "decision table" framing. - CHANGELOG: add a bullet for the ViewModel-nav guidance change so 4.2.1 isn't read as table-only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Zc7mbSrnbAsnVi3tSsBAs
…urces Cross-checked the decision-table API claims against developer.android.com: NavDisplay/entryProvider (basics), rememberNavBackStack + NavKey + @serializable and both NavEntryDecorators incl. the lifecycle-viewmodel-navigation3 artifact (save-state), and SinglePaneSceneStrategy/ListDetailSceneStrategy (scenes). All confirmed. Scene strategies group entries by window size and do NOT register deep links — re-confirming the round-3 fix. Added the basics + save-state pages to Primary Sources and annotated the scenes link so the deep-link distinction is sourced. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Zc7mbSrnbAsnVi3tSsBAs
…lt claim Verified every API shape in navigation.md against the official android/nav3-recipes samples and developer.android.com. The bulk was already correct (artifacts incl. lifecycle-viewmodel-navigation3, @serializable + NavKey destinations, rememberNavBackStack, entryProvider DSL, decorator ordering, intent->key deep links). Corrections from the ground-truth pass: - VM-driven navigation: drop the overstated "model as durable UI state" rule, which our flows.md opinion presented as the answer and which contradicts the official results recipes. Reframe around the invariant every recipe upholds: the back stack is mutated in the route, never in a ViewModel. For results handed back from a child screen, point at Nav3's actual ResultEventBus (conflateAsState OR ResultEffect — both official); flows.md stays as a preference for must-not-lose outcomes, not a hard rule. Split the conflated decision-table row into "navigate off a VM signal" vs "pass a result back". - Add accurate detail the recipes show: sceneStrategies = listOf( rememberListDetailSceneStrategy()) + entry metadata listPane()/detailPane() (adaptive strategy in material3.adaptive:adaptive-navigation3); entryDecorators replaces defaults so the saveable decorator must be re-listed; dropUnlessResumed click guard; deep-link = own the intent->key mapping (no DSL). - Add a Nav3-alpha (1.x-alpha) caveat and cite the nav3-recipes repo as a source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Zc7mbSrnbAsnVi3tSsBAs
🟢 Codex docs-verified review (Nav3 against official sources)Blocker
Should-Fix
Nit
Accurate
re-review with android/nav3-recipes + developer.android.com context · requested by author |
🔵 Cursor docs-verified review (Nav3 against official sources)PR #8 verification —
|
| Claim | Verdict |
|---|---|
Nav3 is alpha (1.x-alpha; recipes pin 1.2.0-alpha02) |
✅ |
Artifacts: navigation3-runtime, navigation3-ui, lifecycle-viewmodel-navigation3, adaptive-navigation3 |
✅ (nav3-recipes/gradle/libs.versions.toml) |
@Serializable types implementing NavKey; args as key fields |
✅ (save-state doc + all saveable recipes) |
entryProvider { entry<T> { … } } DSL |
✅ (basicdsl/BasicDslActivity.kt) |
backStack.add / removeLastOrNull; no NavController in Nav3 |
✅ |
| VM must not own stack; UI/route mutates stack | ✅ (recipes + conditional/Navigator.kt — navigator holds stack, called from composables) |
dropUnlessResumed { backStack.add(...) } |
✅ (every basic recipe) |
entryDecorators replaces defaults; re-add rememberSaveableStateHolderNavEntryDecorator() when adding VM decorator |
✅ (save-state doc snippet + BasicViewModelsActivity.kt comment) |
rememberViewModelStoreNavEntryDecorator() import/package |
✅ (androidx.lifecycle.viewmodel.navigation3) |
ListDetailSceneStrategy + pane metadata; adaptive artifact |
✅ (material/listdetail/MaterialListDetailActivity.kt) |
SinglePaneSceneStrategy is default |
✅ (NavDisplay.kt default sceneStrategies = listOf(SinglePaneSceneStrategy()); scenes doc) |
ResultEventBus: conflateAsState and ResultEffect both official |
✅ (results/state/, results/event/) |
Deep links: intent Uri → match → decode NavKey → rememberNavBackStack(key); no Nav3 deep-link DSL; scenes don't register URIs |
✅ (deeplink/basic/MainActivity.kt, docs/deeplink-guide.md) |
Nav2: composable<T>, toRoute(), no string routes, navDeepLink |
✅ |
| Decision table Nav3/Nav2 labeling | ✅ — rows are scoped; LLM tell is helpful |
| Deep Links section fix (old “register on entry via scene strategies” removed) | ✅ — now correct |
Blocker
None for factual/API misrepresentation. The doc is substantially aligned with official guidance.
Should-fix
1. rememberNavBackStack<NavKey>(Home) in the main example — navigation.md:41
Library API (Android):
fun rememberNavBackStack(vararg elements: NavKey): NavBackStack<NavKey>Official samples call rememberNavBackStack(Home) with no type argument (basicsaveable, save-state doc). The generic call rememberNavBackStack<NavKey>(Home) is not the library signature; it would not compile against androidx.navigation3. The typed form exists only as a recipe-local extension in conditional/ConditionalActivity.kt (with an issuetracker note).
Fix: use rememberNavBackStack(Home). If you want NavBackStack<MyAppNavKey>, document the custom rememberMyAppNavBackStack wrapper from the save-state doc — don't imply the library overload is generic.
2. Result decorator + saveable default — navigation.md:77–79
You correctly warn that custom entryDecorators replace defaults for the VM case. Official result recipes use only rememberResultEventBusNavEntryDecorator() (no saveable decorator). That means rememberSaveable inside entries may not work unless saveable is re-listed — same rule as VM, but unstated for results.
3. README / PR body wording — README.md (not navigation.md)
README says “ViewModel-driven navigation as durable state”; navigation.md:76 says the route mutates the stack and the VM must not get backStack. PR description also still mentions Flow<NavEvent> while the reference removed that pattern. Misleading metadata, not a Nav3 API error.
Nit
| Item | Note |
|---|---|
navigation.md:68 |
rememberListDetailSceneStrategy() compiles (optional directive); recipes pass directive for adaptive pane layout — fine as shorthand. |
navigation.md:84 |
Predictive back + “override TransitionSpec” is directionally right; details live in animations recipes. |
| Decision table list-detail row | Doesn't mention extraPane() (used in material list-detail recipe for a third pane) — optional enhancement. |
| Open polymorphism | Save-state doc requires a SerializersModule on non-Android / open hierarchies; doc doesn't mention it — edge case. |
| Nav2 “2.8+” | Correct floor; recipes use Nav2 2.9.7. |
| Result process death | Doc correctly nudges toward flows.md for durable outcomes; could explicitly say ResultEventBus state is not process-death safe (your verified extract) — one line would tighten it. |
Section-by-section
- Decision table — Accurate and well-scoped Nav3 vs Nav2 vs plain state. Bottom-nav → Nav3 matches
multiplestacksrecipe. - Nav3 model + guardrails — Accurate except the
rememberNavBackStackexample syntax (Should-fix docs: add Codex install symlink instructions #1). - Nav2 type-safe — Accurate example and guardrails.
- Common mistakes — Accurate; VM/backStack split is correct.
- Deep links — Accurate after this PR's rewrite.
- Primary sources — Good links; nav3-recipes callout is appropriate.
Bottom line: The PR's Nav3 guidance is largely docs-verified and fixes a real prior error on deep links/scene strategies. The one substantive code-shape issue is rememberNavBackStack<NavKey>(…) — change to the library's non-generic call. Everything else is polish or PR/README copy consistency.
link-mode re-review: reviewer fetched PR #8 + android/nav3-recipes + developer.android.com · requested by author
Two docs-context cross-reviews (Codex + Cursor, each fetching android/nav3-recipes + developer.android.com) converged on a real code error and a few refinements: - BLOCKER (Codex) / Should-fix (Cursor): `rememberNavBackStack<NavKey>(Home)` does NOT compile — the library function is `rememberNavBackStack(vararg elements: NavKey): NavBackStack<NavKey>`, not generic (the typed form is only a recipe-local extension). Fixed the example to `rememberNavBackStack(Home)` and added a guardrail so it isn't "corrected" back to the generic form. - ResultEventBus accuracy (Codex + Cursor): the `conflateAsState` result is the latest value within the current navigation lifetime and is NOT retained across process death — say so instead of implying it's the durable choice. - Narrowed "never in a ViewModel": don't make a feature/screen VM own/mutate the back stack; a dedicated app-level navigation holder that owns the stack (recipes' Navigator/NavigationState) is a legitimate, separate pattern. - Deep-link table row: "seed it as the start key (or push for a new intent)". - README + PR body: drop the stale "ViewModel-driven navigation as durable state" / Flow<NavEvent> wording; align with the route-mutates-stack + ResultEventBus model. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Zc7mbSrnbAsnVi3tSsBAs
🔵 Cursor review (automated cross-review)PR #8 review — Navigation 3 decision tableVerified against BlockerNone. The prior Should-fix
Nit
Checklist (your 8 points)
Overall: Looks good — ship after tightening the via pr-review-relay · author: claude · context: nav3-context.md |
🟠 Antigravity review (automated cross-review)The PR looks excellent and perfectly matches the official Nav3 documentation and via pr-review-relay · author: claude · context: nav3-context.md |
…example Round of docs-context relay (link mode): Cursor no-blocker (confirmed the rememberNavBackStack fix), Antigravity clean. Two should-fixes applied: - Generalize the "entryDecorators replaces all built-in decorators" rule into its own guardrail instead of stating it only for the ViewModel decorator. The ResultEventBus bullet now re-lists rememberSaveableStateHolderNavEntryDecorator() too, with a note that the official result recipes omit it only because those screens don't use rememberSaveable (Cursor #1). - Align the golden-path example with the dropUnlessResumed guardrail via a comment at the navigation callbacks (Cursor #2). - Nit: show rememberListDetailSceneStrategy<NavKey>() and mention extraPane() to match the Material list-detail recipe. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Zc7mbSrnbAsnVi3tSsBAs
🟢 Codex review (automated cross-review)Blocker Should-fix
Nit Everything else I checked lines up with the official sources: non-generic via pr-review-relay · author: claude · context: nav3-context.md |
…eview) Codex (link mode, diff-fallback now working) confirmed everything else lines up and flagged that the SHORT references still said "never" own/mutate the back stack in a ViewModel — stronger than the save-state doc, which lists an app-level holder owning the stack as valid. The main guardrail already carved this out; align the decision-table row, Common Mistakes, README, and CHANGELOG to "screen/feature ViewModel" with a nod to the app-level-holder carve-out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Zc7mbSrnbAsnVi3tSsBAs
What
Adds a "when to use" decision table to
skills/compose-agent/references/navigation.mdas a workflow entry point, in the same shape as thepaging.mddecision table, plus revised Nav3 guidance.The table routes the agent to the right approach before it writes nav code:
@SerializableNavKeyfields (Nav3) /toRoute()(Nav2), never string interpolationbackStack.add/removeLastOrNull); the back stack is never injected into a screen ViewModelResultEventBus(stateconflateAsStateor one-shotResultEffect— both official; state is not process-death-safe)ViewModelscope (rememberViewModelStoreNavEntryDecorator(), re-listing the saveable default),BackHandler, deep links,ListDetailSceneStrategyvsSinglePaneSceneStrategyPlus an LLM tell: reaching for
NavController/NavHost/navigate(route)in new Nav3 code, or a nav library for a screen whose "navigation" is internal state.Verification
Every API shape was checked against the official
android/nav3-recipessamples (which compile) anddeveloper.android.com. Two cross-review rounds with docs context caught a real one —rememberNavBackStackis not generic (vararg NavKey), so the example now callsrememberNavBackStack(Home). Nav3 is flagged as alpha (1.x-alpha).Why
From android/skills#50: Navigation 3 guidance was index-only without a workflow entry point.
Versions
compose-agent→4.2.1.jetpack-compose-auditunchanged at4.2.0.bin/cipasses.🤖 Generated with Claude Code