Skip to content

Fix push list resetting to latest 10 pushes on query param changes - #9721

Open
camd wants to merge 1 commit into
masterfrom
camd/push-reset-url-drift
Open

Fix push list resetting to latest 10 pushes on query param changes#9721
camd wants to merge 1 commit into
masterfrom
camd/push-reset-url-drift

Conversation

@camd

@camd camd commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Problem

Users paging back through pushes ("get next N") or toggling filters would sporadically have the jobs view clear and re-render — sometimes resetting to the latest 10 pushes — losing their place while searching for a push. There was no clear STR; it "seemed related to adding or removing query string parameters."

Root cause

The app had URL writers that React Router never saw:

  • replaceLocation() / setUrlParam() (ui/helpers/location.js) wrote the URL with a raw window.history.pushState (despite the name), with no popstate dispatch.
  • PushList.fetchNextPushes did the same when rewriting revision=Xtochange=X (and dropping startdate) for "get next N".

React Router's location.search (and PushList's prevRouterSearch) went stale. The next popstate-dispatching action — clicking a job, clicking empty push-list space (clear-selection handler), toggling a filter, or the back button — woke the router, and PushList.handleUrlChanges compared the stale search against the new one, misreading the long-gone revision/startdate params as a range change. That triggered updateRange()clearPushes() + fetchPushes(): the whole push list wiped and refetched, falling back to DEFAULT_PUSH_COUNT = 10 when the URL had no fromchange. The outcome depended on how raw writes interleaved with router-visible navigations, which is why no reliable STR existed.

Deterministic STR (reproducible on production): open /jobs?repo=autoland&revision=<sha>, click "get next 10", then click any empty space in the push list → entire page clears and refetches.

Fix

Single-writer principle — every URL mutation is now visible to React Router:

  • replaceLocation() delegates to replaceUrlSearch(): true replaceState semantics (it previously pushed, bloating history on every fromchange rewrite and param normalization) plus a popstate dispatch so the router stays in sync.
  • fetchNextPushes uses updateUrlSearch() and preemptively updates prevRouterSearch so the intentional revision → tochange rewrite is not treated as a range change.

Side benefit: browser Back after "get next" now lands on a consistent single-revision state, since the address bar and router agree at every step.

Tests

  • New tests/ui/job-view/PushListUrlDrift_test.jsx reproduces both the router/URL drift and the spurious clear-and-refetch. It deliberately uses BrowserRouterMemoryRouter cannot observe raw history writes, which is why existing tests never caught this. Both tests fail on master and pass with this fix.
  • pushes_test.jsx updated to assert replaceState instead of the old raw pushState implementation detail; PushList_test.jsx now stubs replaceState alongside pushState to keep the jsdom URL isolated between tests.
  • Full frontend suite green: 92 suites / 1036 tests.
  • Hand-verified in the browser: bug reproduces on production, does not reproduce with this branch.

replaceLocation() and PushList.fetchNextPushes wrote the URL with raw
window.history.pushState, which React Router never sees. The next
popstate-dispatching action (job click, mousedown on empty push-list
space, filter toggle, back button) woke the router with a stale
location.search, and PushList.handleUrlChanges misread the long-gone
revision/startdate params as a range change, triggering clearPushes()
and a refetch -- resetting to the latest 10 pushes when no fromchange
was present.

- replaceLocation() now uses replaceUrlSearch(): true replaceState
  semantics (it previously pushed, despite its name) plus a popstate
  dispatch so React Router stays in sync.
- fetchNextPushes() uses updateUrlSearch() and preemptively updates
  prevRouterSearch so the intentional revision->tochange rewrite is not
  treated as a range change.
- New regression test PushListUrlDrift_test.jsx uses BrowserRouter
  (MemoryRouter cannot observe raw history writes) to reproduce the
  drift and the spurious clear/refetch deterministically.
@camd camd self-assigned this Jul 24, 2026
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.

1 participant