Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

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

Closed
#507 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

A pull request for this has already been merged.

  • #548 by @Brooooooklyn — merged

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
55/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
node.js, playwright, typescript
Domain
frontend, testing

Research direction

Read packages/void/src/pages/navigation-engine.ts around makeCommit() and packages/void/src/pages/client.ts around applyNavigationResource(). Run the reported parallel-integration Playwright test and inspect how it controls adapter update timing. Add regression coverage for both completion orders and the normal-navigation control; done means a superseded navigation cannot change the URL, head, scroll, or Deferred ownership, and the reproduction leaves both page and URL at /b.

Written by the indexing model from the issue text.

Description

Description

An earlier navigation can commit its URL after a later navigation has already rendered and committed. This leaves the displayed page and browser URL pointing to different destinations.

Unlike #506, this occurs with default navigation options, without preserveState: true.

Environment

  • Commit: 2ceab89154dc6e0c3fa5525f7bd4d1d5f3d0aa64
  • Package version: 1.1.12
  • OS: Ubuntu 24.04.3 LTS x86_64
  • Node: 24.21.0
  • Vite+: 1.3.0; Vitest: 5.0.5
  • Playwright: 1.64.1
  • Chromium Headless Shell: 134.0.6998.35

Steps to reproduce

  1. Initialize the production createVoidRouter() in a browser fixture with valid Pages responses for /a and /b, with View Transitions disabled.
  2. Provide an adapter whose applyUpdate() updates the displayed page but returns a controllable Promise for /a:
let releaseA!: () => void;
let enteredA!: () => void;

const gate = new Promise<void>(resolve => {
  releaseA = resolve;
});
const entered = new Promise<void>(resolve => {
  enteredA = resolve;
});

const adapter = {
  // Other adapter methods omitted.
  applyUpdate(pageData: { url: string }) {
    document.getElementById('app')!.textContent = pageData.url;

    if (pageData.url === '/a') {
      enteredA();
      return gate;
    }
  },
};
  1. Start navigation A, allow B to finish, and then release A:
const visitA = router.visit('/a');
await entered;

await router.visit('/b');

releaseA();
await visitA;
  1. Compare the displayed page with window.location.pathname.

Actual behavior

The older navigation overwrites the URL after the newer navigation has committed:

Value Result
Displayed page /b
window.location.pathname /a

A newly added Playwright integration test reproduced this in both runs. The normal-navigation control passed in both runs.

The test was executed through the repository's parallel-integration project:

vp test run packages/void/test/integration/pages-navigation-race.test.ts --project parallel-integration

The fixture serves the production Pages client through a local HTTP server and controls the adapter's update timing. This verifies the Core/browser race; it is not a full production-build test across every framework adapter.

Expected behavior

The displayed page and browser URL should both remain /b.

Once B has superseded A and committed, completing A's pending adapter update should not allow A to commit its URL or other navigation side effects.

Relevant implementation

In packages/void/src/pages/navigation-engine.ts, makeCommit() uses the pending resource ID when clearing pending state, but that check does not guard the remaining commit operations.

applyNavigationResource() in packages/void/src/pages/client.ts awaits the adapter update and then calls resource.commit(). Consequently, the older resource can still reach the history-writing path after the newer resource has committed.

Acceptance criteria

  • A superseded navigation cannot overwrite the URL after a newer navigation commits.
  • The reproduction above leaves both the displayed page and URL at /b.
  • A superseded commit does not overwrite the newer page's head, scroll, or Deferred resource ownership.
  • Regression coverage includes controlled adapter-update completion orders and a normal-navigation control.
Dominant language
TypeScript
Stars
580
Forks
56
Avg merge
11h 32m
Merged PRs (30d)
158

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from voidzero-dev/void

All issues in voidzero-dev/void

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.