Repository navigation
Commit cd480dd
fix(pages): keep a newer visit's URL when an older one finishes later (#548)
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](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>1 parent 70c275d commit cd480dd
4 files changed
Lines changed: 330 additions & 10 deletions
File tree
- packages
- react/src/runtime
- void
- src/pages
- test/unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
437 | 437 | | |
438 | 438 | | |
439 | 439 | | |
| 440 | + | |
440 | 441 | | |
441 | 442 | | |
442 | 443 | | |
| |||
459 | 460 | | |
460 | 461 | | |
461 | 462 | | |
462 | | - | |
| 463 | + | |
463 | 464 | | |
464 | 465 | | |
465 | 466 | | |
| |||
468 | 469 | | |
469 | 470 | | |
470 | 471 | | |
471 | | - | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
472 | 483 | | |
473 | 484 | | |
474 | 485 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
357 | 357 | | |
358 | 358 | | |
359 | 359 | | |
| 360 | + | |
360 | 361 | | |
361 | 362 | | |
362 | | - | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
363 | 371 | | |
364 | 372 | | |
365 | 373 | | |
366 | 374 | | |
367 | 375 | | |
368 | | - | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
369 | 380 | | |
370 | 381 | | |
371 | 382 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
104 | 104 | | |
105 | 105 | | |
106 | 106 | | |
107 | | - | |
| 107 | + | |
| 108 | + | |
108 | 109 | | |
109 | 110 | | |
110 | 111 | | |
| |||
252 | 253 | | |
253 | 254 | | |
254 | 255 | | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
255 | 259 | | |
256 | 260 | | |
257 | 261 | | |
| |||
853 | 857 | | |
854 | 858 | | |
855 | 859 | | |
856 | | - | |
| 860 | + | |
857 | 861 | | |
858 | 862 | | |
859 | 863 | | |
860 | | - | |
| 864 | + | |
861 | 865 | | |
862 | 866 | | |
863 | 867 | | |
864 | 868 | | |
865 | | - | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
866 | 875 | | |
| 876 | + | |
867 | 877 | | |
868 | 878 | | |
869 | 879 | | |
| |||
922 | 932 | | |
923 | 933 | | |
924 | 934 | | |
| 935 | + | |
925 | 936 | | |
926 | 937 | | |
927 | 938 | | |
| |||
993 | 1004 | | |
994 | 1005 | | |
995 | 1006 | | |
996 | | - | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
997 | 1012 | | |
998 | 1013 | | |
999 | 1014 | | |
1000 | 1015 | | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
1001 | 1022 | | |
1002 | 1023 | | |
1003 | 1024 | | |
| |||
1012 | 1033 | | |
1013 | 1034 | | |
1014 | 1035 | | |
1015 | | - | |
| 1036 | + | |
1016 | 1037 | | |
1017 | 1038 | | |
1018 | 1039 | | |
| |||
0 commit comments