Skip to content

fix(pages): keep a newer visit's URL when an older one finishes later - #548

Merged
Brooooooklyn merged 1 commit into
mainfrom
fix/pages-superseded-commit
Oct 22, 2026
Merged

Brooooooklyn merged 1 commit into
mainfrom
fix/pages-superseded-commit

Conversation

@Brooooooklyn

Copy link
Copy Markdown
Member

Fixes #507

Summary

An older visit whose page update was still running could commit after a newer visit had already committed. It then wrote its URL, head and scroll over the newer page, and disposed the newer page's Deferred props.

before:  visit('/a') ── applyUpdate(A) ……… slow ……… commit(A) ✗ URL /a, title A, B's Deferred rejected
                          visit('/b') ── applyUpdate(B) ── commit(B)   page /b

after:   commit(A) sees that a newer visit already set the URL → skipped, commit() returns false

Rule: the address bar only moves forward

makeCommit() in packages/void/src/pages/navigation-engine.ts tracks the newest visit whose URL is in the address bar:

sets it why
a successful commit its URL was just written
a back/forward visit, when it starts the browser has already moved
a same-page hash visit it renders nothing, so a page already on screen still commits its URL

An older visit commits only if no newer visit has set the URL. resource.commit() now resolves true/false.

Cases

case result
A finishes after B committed (the issue) A skipped; page, URL, head, scroll and B's Deferred props stay on B
A finishes while B is still loading A commits /a (its page is on screen), then B commits /b
the new page's mount code calls visit('/b', { replace: true }) A pushes /a, B replaces it; the previous entry is kept
Back pressed while A is being applied A never pushes, so forward history is kept
a hash link (or Back to a same-page hash entry) during A's update A still commits /a, as before, so page and URL match

I first tried "skip A if any newer visit started" (what Inertia and SvelteKit do). Review showed it breaks redirects made from a page's mount code: the redirect's replace then overwrote the previous page's entry. So the rule only looks at visits that have already set the URL.

Callers

  • client.ts (Vue, Svelte, Solid): a skipped visit throws an AbortError inside applyNavigationResource. The existing catch settles it like any abandoned visit: GET resolves undefined, other methods reject with AbortError (useForm already ignores that). A view-transition callback that runs after a newer visit started now skips applyUpdate, so the older page cannot paint over the newer one.
  • React (pages-client.ts): finishNavigation uses the result. A skipped visit settles the same way and does not dispatch finish.

No docs change: nothing changes for users except the bug.

Tests

New describe('superseded navigation commits (#507)') in packages/void/test/unit/pages-client.test.ts (10 cases):

  • normal visits in order (control)
  • the issue's repro: URL, router.url, title, scroll and B's Deferred prop stay on B
  • A finishing before B commits
  • a redirect started from the new page's mount
  • a hash visit during A's update, and Back to a same-page hash entry during it
  • Back to another page during A's update
  • a superseded form submission rejects with AbortError
  • a late view-transition callback
  • the engine called the way the React adapter calls it

Each guard has a test that fails when the guard is removed. 5 of them fail on main. vp test run passed except one platform browser test that timed out at 5s under full load. That test runs with JavaScript turned off, so this change doesn't reach it; it passes alone and in its project run twice. vp run test, vp run check, vp lint and vp fmt pass.

🤖 Generated with Claude Code

An older visit whose page update was still running could commit after a
newer visit had committed. It wrote its URL, head and scroll over the
newer page and disposed the newer page's Deferred props (#507).

The engine now tracks the newest visit whose URL is in the address bar:
the last commit, or a back/forward visit the browser has already moved
to. An older commit is skipped, and commit() reports false.

- A visit started while a page mounts (for example a redirect) still
  lets that page commit first, so a replace targets the right entry.
- Same-page hash visits do not count, so a page already on screen keeps
  its URL in step with it.
- The core router skips a view transition callback that a newer visit
  overtook, and settles a skipped visit like an abandoned one: GET
  resolves undefined, other methods reject with AbortError. React
  settles the same way.

Fixes #507

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

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-22T08:27:35.699508Z bc3d594 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://fix-pages-superseded-commit-void-cloud.voidzero-docs.workers.dev (commit bc3d594)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://f38e0a79-void-cloud.voidzero-docs.workers.dev bc3d594 2026-10-22T08:28:44.255Z Visit the dashboard ↗

@Brooooooklyn
Brooooooklyn merged commit cd480dd into main Oct 22, 2026
20 checks passed
@Brooooooklyn
Brooooooklyn deleted the fix/pages-superseded-commit branch October 22, 2026 10:19
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.

An older navigation can overwrite the URL after a newer navigation commits

1 participant