[PR #1102] fix(router): gate refresh and traverse outcomes #1096

Open
opened 2026-05-06 13:11:58 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/1102
Author: @NathanDrake2406
Created: 5/6/2026
Status: 🔄 Open

Base: mainHead: nathan/issue-726-lifecycle-refresh-prefetch-traverse


📝 Commits (1)

  • 507ffe6 fix(router): gate refresh and traverse outcomes

📊 Changes

8 files changed (+322 additions, -24 deletions)

View changed files

📝 packages/vinext/src/server/app-browser-entry.ts (+4 -2)
📝 packages/vinext/src/server/app-browser-navigation-controller.ts (+11 -13)
📝 packages/vinext/src/server/app-browser-state.ts (+10 -0)
📝 packages/vinext/src/server/app-browser-visible-commit.ts (+9 -2)
📝 packages/vinext/src/shims/navigation.ts (+10 -3)
📝 packages/vinext/src/shims/next-shims.d.ts (+23 -1)
📝 tests/app-browser-entry.test.ts (+203 -2)
📝 tests/prefetch-cache.test.ts (+52 -1)

📄 Description

What changed

Implements #726-CORE-05/06A and #726-CORE-06B from issue #726.

  • Treat refresh, prefetch, and traverse completion as lifecycle outcomes instead of assuming every resolved payload is still allowed to affect the visible router tree.
  • Gate pending refresh, traverse, navigation, and same-URL navigation commits by both active navigation id and the visible commit version captured when the operation started.
  • Return explicit browser navigation payload outcomes: committed, no-commit, and hard-navigate. Stale and hard-navigation payloads now settle pending router state without dispatching visible UI updates.
  • Only seed the visited-response cache after a payload actually commits, so late refresh or traverse results cannot masquerade as rendered responses.
  • Make router.prefetch expose cache-seeded only after the RSC snapshot is ready and pending work has been cleared, and consume entries only in that settled state.
  • Update the next/navigation ambient shim declarations to match the runtime prefetch cache shape.

Why

Issue #726 calls out a class of stale async navigation results where an older operation can resolve after a newer visible commit. The previous gate only checked the active navigation id. That was not enough for operations such as refresh and traverse, because a newer visible commit can occur without changing the same operation token. The existing pending operation shape already captured startedVisibleCommitVersion, so this patch uses that value as the lifecycle boundary.

For prefetch, the required outcome is cache seed only. The cache entry now stays pending until the response snapshot is actually consumable, and prefetch still never calls the visible navigation entry point.

Validation

  • vp test run tests/app-browser-entry.test.ts tests/prefetch-cache.test.ts
  • vp test run tests/shims.test.ts -t "useRouter|prefetch|refresh"
  • vp check packages/vinext/src/server/app-browser-state.ts packages/vinext/src/server/app-browser-visible-commit.ts packages/vinext/src/server/app-browser-navigation-controller.ts packages/vinext/src/server/app-browser-entry.ts packages/vinext/src/shims/navigation.ts packages/vinext/src/shims/next-shims.d.ts tests/app-browser-entry.test.ts tests/prefetch-cache.test.ts
  • git diff --check

Non-goals

  • Full refresh lane model or cache reuse authority.
  • Full back/forward intent model or Activity preservation.

Bonk: please read issue #726 for the big picture before reviewing this patch. The lifecycle-gate work here is one slice of that larger router determinism plan.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/1102 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 5/6/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `nathan/issue-726-lifecycle-refresh-prefetch-traverse` --- ### 📝 Commits (1) - [`507ffe6`](https://github.com/cloudflare/vinext/commit/507ffe6fef87b7dfd2641bedacba83b89c444613) fix(router): gate refresh and traverse outcomes ### 📊 Changes **8 files changed** (+322 additions, -24 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/server/app-browser-entry.ts` (+4 -2) 📝 `packages/vinext/src/server/app-browser-navigation-controller.ts` (+11 -13) 📝 `packages/vinext/src/server/app-browser-state.ts` (+10 -0) 📝 `packages/vinext/src/server/app-browser-visible-commit.ts` (+9 -2) 📝 `packages/vinext/src/shims/navigation.ts` (+10 -3) 📝 `packages/vinext/src/shims/next-shims.d.ts` (+23 -1) 📝 `tests/app-browser-entry.test.ts` (+203 -2) 📝 `tests/prefetch-cache.test.ts` (+52 -1) </details> ### 📄 Description ## What changed Implements #726-CORE-05/06A and #726-CORE-06B from issue #726. - Treat refresh, prefetch, and traverse completion as lifecycle outcomes instead of assuming every resolved payload is still allowed to affect the visible router tree. - Gate pending refresh, traverse, navigation, and same-URL navigation commits by both active navigation id and the visible commit version captured when the operation started. - Return explicit browser navigation payload outcomes: committed, no-commit, and hard-navigate. Stale and hard-navigation payloads now settle pending router state without dispatching visible UI updates. - Only seed the visited-response cache after a payload actually commits, so late refresh or traverse results cannot masquerade as rendered responses. - Make router.prefetch expose cache-seeded only after the RSC snapshot is ready and pending work has been cleared, and consume entries only in that settled state. - Update the next/navigation ambient shim declarations to match the runtime prefetch cache shape. ## Why Issue #726 calls out a class of stale async navigation results where an older operation can resolve after a newer visible commit. The previous gate only checked the active navigation id. That was not enough for operations such as refresh and traverse, because a newer visible commit can occur without changing the same operation token. The existing pending operation shape already captured startedVisibleCommitVersion, so this patch uses that value as the lifecycle boundary. For prefetch, the required outcome is cache seed only. The cache entry now stays pending until the response snapshot is actually consumable, and prefetch still never calls the visible navigation entry point. ## Validation - vp test run tests/app-browser-entry.test.ts tests/prefetch-cache.test.ts - vp test run tests/shims.test.ts -t "useRouter|prefetch|refresh" - vp check packages/vinext/src/server/app-browser-state.ts packages/vinext/src/server/app-browser-visible-commit.ts packages/vinext/src/server/app-browser-navigation-controller.ts packages/vinext/src/server/app-browser-entry.ts packages/vinext/src/shims/navigation.ts packages/vinext/src/shims/next-shims.d.ts tests/app-browser-entry.test.ts tests/prefetch-cache.test.ts - git diff --check ## Non-goals - Full refresh lane model or cache reuse authority. - Full back/forward intent model or Activity preservation. Bonk: please read issue #726 for the big picture before reviewing this patch. The lifecycle-gate work here is one slice of that larger router determinism plan. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#1096
No description provided.