Skip to content

fix(frontend): deep-linked switches frozen — commit selection via History API#9

Merged
bvdmitri merged 1 commit into
mainfrom
fix/deeplink-switches-history-api
Jun 17, 2026
Merged

fix(frontend): deep-linked switches frozen — commit selection via History API#9
bvdmitri merged 1 commit into
mainfrom
fix/deeplink-switches-history-api

Conversation

@bvdmitri

Copy link
Copy Markdown
Member

Problem

Opening a shared link that already carries query params (e.g.
https://benchmarks.rxinfer.com/?b=basic%2Fcoin_toss&hw=rpi5-8gb) left every
selector dead — the hardware/Julia/scenario/metric switchers and the left sidebar
examples. Selecting anything did nothing. Starting fresh from the bare
benchmarks.rxinfer.com (no params) worked. First reported in Safari, but reproduces
in Chrome too.

Root cause

All dashboard state lives in query params via useSelection(), and every selector
funnels through its single select() function, which committed each change with
router.replace(...). That runs the App Router's navigation/transition machinery. On
this static export (output: "export"), a query-only router.replace on a
deep-linked load silently no-ops — no exception, it just doesn't navigate, so
useSearchParams() never updates and the controlled <Select>s/buttons stay frozen.
One broken commit function = every selector frozen at once.

Fix

Commit selections with window.history.replaceState instead of router.replace.
Next.js syncs useSearchParams/usePathname with pushState/replaceState and skips
the route transition — the documented idiom for query-param-only state updates. URL
building is unchanged; replaceState (no new history entry) preserves the prior
router.replace behavior.

Verification

  • Unit: useSelection + DashboardPage tests updated to assert on history.replaceState; full suite 161 passing, tsc + eslint clean.
  • End-to-end in real headless Chromium against a production static build (data from raw.githubusercontent.com), opening the exact deep link above:
Hardware switch Sidebar example
Old router.replace URL unchanged ❌ URL unchanged ❌
Fixed history.replaceState hw=rpi5-8gbhw=github-actions-ubuntu b=coin_tossb=iid_normal

No console/page errors in either build.

🤖 Generated with Claude Code

…es work

Opening a shared link that already carries query params (e.g.
?b=basic/coin_toss&hw=rpi5-8gb) left every switch dead — selecting a value did
nothing, while a fresh load with no params worked. useSelection committed each
selection through router.replace, which runs the App Router's navigation/
transition machinery. Under static export (output: export), that path fails to
update the URL on a deep-linked load (most visibly in Safari), so
useSearchParams never changes and the controlled Selects stay frozen.

Switch to window.history.replaceState, which Next.js syncs with
useSearchParams/usePathname without a route transition — the documented idiom
for query-param-only state updates. URL-building logic is unchanged. Tests now
spy on history.replaceState instead of router.replace.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bvdmitri
bvdmitri merged commit bc48939 into main Jun 17, 2026
20 checks passed
@bvdmitri
bvdmitri deleted the fix/deeplink-switches-history-api branch June 17, 2026 07:53
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