Skip to content

ref: unify optimistic state into a single overlay store - #1482

Draft
franky47 wants to merge 10 commits into
nextfrom
ref/single-store
Draft

ref: unify optimistic state into a single overlay store#1482
franky47 wants to merge 10 commits into
nextfrom
ref/single-store

Conversation

@franky47

@franky47 franky47 commented Jul 7, 2026

Copy link
Copy Markdown
Member

Implements steps 1 to 4 of the single-store strategy. The cross-hook typed-value emitter and the queued-query store were two channels describing the same pending URL state, which duplicated machinery, hid a cross-parser adoption bug, and stood in the way of key isolation work.

  • One per-key overlay notification channel on the throttle queue replaces lib/sync.ts. Hooks read a merged raw value (pending overlay, falling back to committed params) through useSyncExternalStore, and sibling hooks re-parse raw queries with their own parser instead of adopting the writer's parsed value.
  • Rate-limit accounting moves onto history.nuqs, so duplicated dist copies share the one Safari History API budget, with a NUQS-409-style duplicate-queue detector.
  • The defaults memo compares by value using the parser's eq, making object and Date dynamic defaults reactive while keeping stable identities and a stable setter. Semver note: apps relying on the frozen-defaults bug will now see defaults update.
  • A shared parse cache keyed by url key, parse function identity and raw query gives cross-consumer referential stability for object parsers.

debounce(Infinity) now behaves like throttle(Infinity): the value lands in the overlay, visible to all hooks on the key, and the flush is deferred. The previous sibling-sync-only behavior was an artifact of the deleted emitter.

The e2e suites were treated as the spec and are untouched: all 2064 tests pass across the 8 cells, plus 419 unit, browser and type tests. Bundle: client 5.70 of 6 kB, minimal tree-shaken 4.19 of 4.5 kB.

Closes #1193

franky47 added 4 commits July 7, 2026 16:37
The typed-value emitter and the queued-query store were two channels describing
the same pending URL state. One per-key channel on the throttle queue removes
the duplication, makes sibling hooks re-parse raw queries with their own parser
(the emitter pushed the writer's parsed value, wrong type on cross-parser
binds), and gives debounce(Infinity) the same all-hooks visibility as
throttle(Infinity).
Duplicated dist copies (bundler or version duplication) each ran their own
throttle accounting against the browser's single History API budget, risking
Safari's SecurityError. Anchoring lastFlushedAt on history.nuqs converges
them, and a module-load counter surfaces the duplication (NUQS-409).
The defaults memo coerced object defaults to '[object Object]', freezing them
at first render. Comparing with the parser's eq keeps identities stable across
renders while letting genuinely-changed defaults through. The setter reads
parsers and defaults through refs so reactive default changes never churn its
identity nor leave clearOnDefault comparing against a stale default.
Hooks binding the same key with the same parser re-parsed identical raw
queries into distinct objects, breaking cross-consumer referential stability
(and causing downstream memo/effect churn). A cache keyed on url key, parse
function identity and raw query yields one shared value; double-binds miss
gracefully, and the server bypasses it to keep requests isolated.
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nuqs Ready Ready Preview, Comment Jul 8, 2026 10:38pm

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 571d332a-5c8c-438f-91fc-ed7e3a7619f4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 7, 2026

Copy link
Copy Markdown
pnpm add https://pkg.pr.new/nuqs@1482

commit: 76344e1

@franky47 franky47 added the AI slop PR generated by AI, needs some cleanup before merging label Jul 7, 2026
@franky47 franky47 added this to the 🪵 Backlog milestone Jul 7, 2026
franky47 added 4 commits July 7, 2026 23:35
The overlay is cleared before updateUrl runs; when the browser rejects the
update (Safari rate limit), hooks would fork silently: the writer kept its
optimistic value while siblings reverted one by one on unrelated re-renders.
Notifying the failed keys makes every hook agree with the URL again, matching
the self-healing the debounce path already had.
Released versions assume `adapters` exists whenever the slot does and crash
on push otherwise; seeding it preserves them under version skew, the exact
duplication scenario the queue detector exists to catch. The 409 message now
also covers same-version copies and non-adapter duplication sources.
Caching a failure as null made the warning a one-shot: a stuck widget could
never be correlated to its malformed URL param after the first parse.
Re-warning on failed-entry hits keeps the signal alive (debug mode only).
Refreshing an existing key at capacity no longer evicts an unrelated entry,
and the server-bypass branch is now pinned by a node test.
The rawValues comment attributed optimistic-update filtering to the identity
gate when it belongs to the queryRef cache; trusting it could get the
load-bearing setter-side queryRef write removed. Also aligns stale wording
(queued-queries naming, writer identity caveat, debug message) and pins the
eq-less object defaults identity churn as the accepted #1193 trade-off.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI slop PR generated by AI, needs some cleanup before merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] dynamic default values do not properly update

1 participant