[PR #1100] fix(router): reject stale same-url server action commits #1095

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

📋 Pull Request Information

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

Base: mainHead: nathan/726-core-04-stale-actions


📝 Commits (2)

  • f2b1e69 fix(router): reject stale same-url server action commits
  • 7b84a43 fix(router): preserve server action initiation version

📊 Changes

5 files changed (+178 additions, -9 deletions)

View changed files

📝 packages/vinext/src/server/app-browser-entry.ts (+8 -1)
📝 packages/vinext/src/server/app-browser-navigation-controller.ts (+5 -7)
📝 packages/vinext/src/server/app-browser-state.ts (+10 -0)
📝 packages/vinext/src/server/app-browser-visible-commit.ts (+7 -1)
📝 tests/app-browser-entry.test.ts (+148 -0)

📄 Description

What this changes

Implements #726-CORE-04 from #726 by rejecting stale same-URL server-action visible commits. A server action that started from an older visible router state can still return its action value, but it can no longer overwrite newer visible state once another commit has advanced the lifecycle version.

Why

Issue #726 calls out that activeNavigationId is not enough for same-URL refresh and server-action races. Same URL does not mean same visible world. Before this change, two same-URL server actions resolving out of order could let the older RSC payload commit after the newer one had already updated the route.

That violated the lifecycle spine invariant from #726: no stale operation commits visible state, and no older server-action result overwrites newer visible state.

Approach

  • Extend the pending commit disposition gate to compare startedVisibleCommitVersion with the current router state visibleCommitVersion.
  • Treat visible-version mismatch as stale before root-boundary hard-navigation decisions, so old action payloads do not trigger stale reload recovery.
  • Read the current browser router state at approval time after async RSC payload resolution, instead of approving against only the state captured before the await.
  • Preserve the existing same-URL server action return-value contract when the visible UI commit is rejected.

Non-goals: this does not model discarded revalidation, redirect lifecycle, cache reuse authority, or the broader NavigationPlanner work from issue #726.

Validation

  • vp test run tests/app-browser-entry.test.ts
  • vp check packages/vinext/src/server/app-browser-navigation-controller.ts packages/vinext/src/server/app-browser-state.ts packages/vinext/src/server/app-browser-visible-commit.ts tests/app-browser-entry.test.ts
  • vp run vinext#build

vp run vinext#build completed with the existing virtual-module external warnings for virtual:vinext-rsc-entry, virtual:vite-rsc/client-references, and private-next-instrumentation-client.

Review note

Bonk: please read issue #726 to see the big picture before reviewing this PR. This is specifically the #726-CORE-04 slice, not the full lifecycle, planner, cache, or compatibility architecture.


🔄 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/1100 **Author:** [@NathanDrake2406](https://github.com/NathanDrake2406) **Created:** 5/6/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `nathan/726-core-04-stale-actions` --- ### 📝 Commits (2) - [`f2b1e69`](https://github.com/cloudflare/vinext/commit/f2b1e69cbc396ffb2900a8163bbde426b771c37b) fix(router): reject stale same-url server action commits - [`7b84a43`](https://github.com/cloudflare/vinext/commit/7b84a431f309f1a3064ced5dfd44c5e49d7eef24) fix(router): preserve server action initiation version ### 📊 Changes **5 files changed** (+178 additions, -9 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/server/app-browser-entry.ts` (+8 -1) 📝 `packages/vinext/src/server/app-browser-navigation-controller.ts` (+5 -7) 📝 `packages/vinext/src/server/app-browser-state.ts` (+10 -0) 📝 `packages/vinext/src/server/app-browser-visible-commit.ts` (+7 -1) 📝 `tests/app-browser-entry.test.ts` (+148 -0) </details> ### 📄 Description ## What this changes Implements #726-CORE-04 from #726 by rejecting stale same-URL server-action visible commits. A server action that started from an older visible router state can still return its action value, but it can no longer overwrite newer visible state once another commit has advanced the lifecycle version. ## Why Issue #726 calls out that `activeNavigationId` is not enough for same-URL refresh and server-action races. Same URL does not mean same visible world. Before this change, two same-URL server actions resolving out of order could let the older RSC payload commit after the newer one had already updated the route. That violated the lifecycle spine invariant from #726: no stale operation commits visible state, and no older server-action result overwrites newer visible state. ## Approach - Extend the pending commit disposition gate to compare `startedVisibleCommitVersion` with the current router state `visibleCommitVersion`. - Treat visible-version mismatch as stale before root-boundary hard-navigation decisions, so old action payloads do not trigger stale reload recovery. - Read the current browser router state at approval time after async RSC payload resolution, instead of approving against only the state captured before the await. - Preserve the existing same-URL server action return-value contract when the visible UI commit is rejected. Non-goals: this does not model discarded revalidation, redirect lifecycle, cache reuse authority, or the broader NavigationPlanner work from issue #726. ## Validation - `vp test run tests/app-browser-entry.test.ts` - `vp check packages/vinext/src/server/app-browser-navigation-controller.ts packages/vinext/src/server/app-browser-state.ts packages/vinext/src/server/app-browser-visible-commit.ts tests/app-browser-entry.test.ts` - `vp run vinext#build` `vp run vinext#build` completed with the existing virtual-module external warnings for `virtual:vinext-rsc-entry`, `virtual:vite-rsc/client-references`, and `private-next-instrumentation-client`. ## Review note Bonk: please read issue #726 to see the big picture before reviewing this PR. This is specifically the #726-CORE-04 slice, not the full lifecycle, planner, cache, or compatibility architecture. --- <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#1095
No description provided.